4 weeks ago
update ucx
--- a/src/server/config/acl.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/acl.c Sun Mar 02 18:10:52 2025 +0100 @@ -57,10 +57,10 @@ } void free_acl_file(ACLFile *conf) { - cxListDestroy(conf->namedACLs); - cxListDestroy(conf->uriACLs); - cxListDestroy(conf->pathACLs); - cxMempoolDestroy(conf->parser.a->data); // TODO: is there a better way to access the mempool? + cxListFree(conf->namedACLs); + cxListFree(conf->uriACLs); + cxListFree(conf->pathACLs); + cxMempoolFree(conf->parser.a->data); // TODO: is there a better way to access the mempool? free(conf); }
--- a/src/server/config/conf.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/conf.c Sun Mar 02 18:10:52 2025 +0100 @@ -34,7 +34,7 @@ int cfg_parse_basic_file(ConfigParser *parser, FILE *in) { parser->lines_begin = NULL; parser->lines_end = NULL; - CxMempool *mp = cxBasicMempoolCreate(512); + CxMempool *mp = cxMempoolCreateSimple(512); CxAllocator *a = (CxAllocator*)mp->allocator; parser->a = a; parser->mp = mp;
--- a/src/server/config/initconf.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/initconf.c Sun Mar 02 18:10:52 2025 +0100 @@ -33,7 +33,7 @@ #include "initconf.h" InitConfig *initconfig_load(const char *file) { - CxMempool *mp = cxBasicMempoolCreate(512); + CxMempool *mp = cxMempoolCreateSimple(512); if(!mp) { return NULL; } @@ -48,13 +48,13 @@ ConfigNode *init_config = serverconfig_load_file(&parser, file); if(!init_config) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } InitConfig *conf = cxMalloc(mp->allocator, sizeof(InitConfig)); if(!conf) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } @@ -89,5 +89,5 @@ } void initconfig_free(InitConfig *conf) { - cxMempoolDestroy(conf->mp); + cxMempoolFree(conf->mp); }
--- a/src/server/config/keyfile.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/keyfile.c Sun Mar 02 18:10:52 2025 +0100 @@ -49,7 +49,7 @@ int r = cfg_parse_basic_file((ConfigParser*)conf, in); if(r != 0) { fclose(in); - cxMempoolDestroy(conf->parser.mp); + cxMempoolFree(conf->parser.mp); free(conf->file); free(conf); // TODO: free @@ -62,7 +62,7 @@ } void free_keyfile_config(KeyfileConfig *conf) { - cxMempoolDestroy(conf->parser.mp); + cxMempoolFree(conf->parser.mp); free(conf->file); free(conf); }
--- a/src/server/config/mimeconf.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/mimeconf.c Sun Mar 02 18:10:52 2025 +0100 @@ -63,7 +63,7 @@ } void free_mime_config(MimeConfig *conf) { - cxMempoolDestroy(conf->parser.a->data); // TODO: is there a better way to access the mempool? + cxMempoolFree(conf->parser.a->data); // TODO: is there a better way to access the mempool? free(conf); }
--- a/src/server/config/objconf.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/objconf.c Sun Mar 02 18:10:52 2025 +0100 @@ -43,7 +43,7 @@ ObjectConfig2* objectconf_load(const char *file) { - CxMempool *mp = cxBasicMempoolCreate(512); + CxMempool *mp = cxMempoolCreateSimple(512); if(!mp) { return NULL; } @@ -61,13 +61,13 @@ ConfigNode *obj_config = serverconfig_load_file(&parser, file); if(!obj_config) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } ObjectConfig2 *conf = cxMalloc(mp->allocator, sizeof(ObjectConfig2)); if(!conf) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } @@ -78,7 +78,7 @@ } void objectconf_free(ObjectConfig2 *objconf) { - cxMempoolDestroy(objconf->mp); + cxMempoolFree(objconf->mp); } int objectconf_validate_directive(ConfigParser2 *parser, ConfigNode *node) {
--- a/src/server/config/serverconfig.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/config/serverconfig.c Sun Mar 02 18:10:52 2025 +0100 @@ -41,7 +41,7 @@ #include <cx/utils.h> ServerConfig* serverconfig_load(const char *file) { - CxMempool *mp = cxBasicMempoolCreate(512); + CxMempool *mp = cxMempoolCreateSimple(512); if(!mp) { return NULL; } @@ -54,13 +54,13 @@ parser.delim = ""; ConfigNode *root = serverconfig_load_file(&parser, file); if(!root) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } ServerConfig *scfg = cxMalloc(mp->allocator, sizeof(ServerConfig)); if(!scfg) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return NULL; } scfg->root = root; @@ -451,7 +451,7 @@ } void serverconfig_free(ServerConfig *cfg) { - cxMempoolDestroy(cfg->mp); + cxMempoolFree(cfg->mp); } ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, cxstring name) {
--- a/src/server/daemon/config.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/config.c Sun Mar 02 18:10:52 2025 +0100 @@ -193,7 +193,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); // load threadpool config log_ereport(LOG_DEBUG, "apply config: Threadpool"); @@ -205,7 +205,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); // check thread pool config if(check_thread_pool_cfg() != 0) { /* critical error */ @@ -228,7 +228,7 @@ /* critical error */ return NULL; } - cxListDestroy(list); + cxListFree(list); // load Listener config log_ereport(LOG_DEBUG, "apply config: Listener"); @@ -239,7 +239,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); // we return here, to let the webserver use the runtime info to // change the uid if needed @@ -271,18 +271,18 @@ cx_foreach(ConfigNode *, logobj, iter) { if(!logobj) { // error - cxListDestroy(list); + cxListFree(list); return NULL; } int ret = cfg_handle_logfile(serverconfig, logobj); if(ret != 0) { // cannot initialize log file - cxListDestroy(list); + cxListFree(list); return NULL; } } - cxListDestroy(list); + cxListFree(list); log_ereport(LOG_DEBUG, "apply config: AccessLog"); list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("AccessLog")); @@ -292,7 +292,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); log_ereport(LOG_DEBUG, "apply config: AuthDB"); list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("AuthDB")); @@ -302,7 +302,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); log_ereport(LOG_DEBUG, "apply config: VirtualServer"); list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("VirtualServer")); @@ -312,7 +312,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); log_ereport(LOG_DEBUG, "apply config: ResourcePool"); list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("ResourcePool")); @@ -322,7 +322,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); log_ereport(LOG_DEBUG, "apply config: Dav"); list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("Dav")); @@ -332,7 +332,7 @@ return NULL; } } - cxListDestroy(list); + cxListFree(list); // set VirtualServer for all listeners CxList *ls = serverconfig->listeners; @@ -342,7 +342,7 @@ // search for VirtualServer //int b = 0; - CxIterator map_iter = cxMapIteratorValues(serverconfig->host_vs); + CxMapIterator map_iter = cxMapIteratorValues(serverconfig->host_vs); cx_foreach(VirtualServer *, vs, map_iter) { if(!cx_strcmp(vsname, (cxstring){vs->name.ptr, vs->name.length})) { listener->default_vs.vs = vs; @@ -701,7 +701,7 @@ int ret = 0; HttpListener *listener = http_listener_create(&lc); if(listener) { - listener->default_vs.vs_name = cx_strdup_a(cfg->a, (cxstring){lc.vs.ptr, lc.vs.length}).ptr; + listener->default_vs.vs_name = cx_strdup_a(cfg->a, lc.vs).ptr; cxListAdd(cfg->listeners, listener); } else { ret = 1; @@ -792,12 +792,12 @@ cxListAdd(backends, node->args); } else { log_ereport(LOG_MISCONFIG, "DavBackend must have only one value"); - cxListDestroy(backends); + cxListFree(backends); return 1; } } else { log_ereport(LOG_MISCONFIG, "DavBackend must be a directive"); - cxListDestroy(backends); + cxListFree(backends); return 1; } } @@ -841,7 +841,7 @@ cxListAdd(repository->davBackends, davInit); } - cxListDestroy(backends); + cxListFree(backends); // initialize vfs cxstring vfs_class = serverconfig_object_directive_value(obj, cx_str("VFS")); @@ -906,7 +906,7 @@ cxListAdd(tokens, &op); } if(cxListAdd(tokens, &arg->value)) { - cxListDestroy(tokens); + cxListFree(tokens); return 1; // OOM } arg = arg->next; @@ -919,7 +919,7 @@ } // don't need the token list anymore - cxListDestroy(tokens); + cxListFree(tokens); return ret; }
--- a/src/server/daemon/event.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/event.c Sun Mar 02 18:10:52 2025 +0100 @@ -81,7 +81,7 @@ void shutdown_eventhandlers_wait(void) { log_ereport(LOG_INFORM, "shutdown eventhandlers"); - CxIterator i = cxMapIteratorValues(event_handler_map); + CxMapIterator i = cxMapIteratorValues(event_handler_map); cx_foreach(EVHandler *, e, i) { evhandler_shutdown(e); } @@ -91,7 +91,7 @@ evhandler_wait_and_destroy(e); } - cxMapDestroy(event_handler_map); + cxMapFree(event_handler_map); log_ereport(LOG_INFORM, "all eventhandlers closed"); }
--- a/src/server/daemon/func.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/func.c Sun Mar 02 18:10:52 2025 +0100 @@ -45,11 +45,11 @@ void func_init() { function_map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256); - function_map->simple_destructor = (cx_destructor_func)funcstruct_free; + function_map->collection.simple_destructor = (cx_destructor_func)funcstruct_free; } void func_cleanup() { - cxMapDestroy(function_map); + cxMapFree(function_map); } void add_function(FuncStruct *func) {
--- a/src/server/daemon/httplistener.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/httplistener.c Sun Mar 02 18:10:52 2025 +0100 @@ -87,13 +87,13 @@ if(!listener_socket_map) { return 1; } - listener_socket_map->simple_destructor = (cx_destructor_func)wssocket_free; + listener_socket_map->collection.simple_destructor = (cx_destructor_func)wssocket_free; return 0; } void http_listener_global_shutdown(void) { - cxMapDestroy(listener_socket_map); + cxMapFree(listener_socket_map); } int start_all_listener() {
--- a/src/server/daemon/httprequest.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/httprequest.c Sun Mar 02 18:10:52 2025 +0100 @@ -683,7 +683,7 @@ void request_free_resources(NSAPISession *sn, NSAPIRequest *rq) { if(!rq->resources) return; - CxIterator i = cxMapIteratorValues(rq->resources); + CxMapIterator i = cxMapIteratorValues(rq->resources); cx_foreach(ResourceData *, resource, i) { resourcepool_free(&sn->sn, &rq->rq, resource); }
--- a/src/server/daemon/keyfile_auth.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/keyfile_auth.c Sun Mar 02 18:10:52 2025 +0100 @@ -64,7 +64,7 @@ cxmutstr *groups, size_t ngroups) { - const CxAllocator *a = keyfile->users->allocator; + const CxAllocator *a = keyfile->users->collection.allocator; if(hash.length < 12) { // hash too short
--- a/src/server/daemon/ldap_auth.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/ldap_auth.c Sun Mar 02 18:10:52 2025 +0100 @@ -488,7 +488,7 @@ CxHashKey key = cx_hash_key(memberValue.ptr, memberValue.length); char *g_member = cxMapGet(group->members, key); if(!g_member) { - cxmutstr member = cx_strdup_a(group->members->allocator, memberValue); + cxmutstr member = cx_strdup_a(group->members->collection.allocator, memberValue); if(!member.ptr) { ret = 1; break; @@ -533,7 +533,7 @@ // OOM ldap_memfree(attribute); // free at least some memory - cxMapDestroy(group->members); + cxMapFree(group->members); pool_free(sn->pool, group); group = NULL; break;
--- a/src/server/daemon/log.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/log.c Sun Mar 02 18:10:52 2025 +0100 @@ -255,7 +255,7 @@ void log_remove_logdup(LogDup *ldup) { pthread_mutex_lock(&mutex); - CxMutIterator i = cxListMutIterator(log_dup_list); + CxIterator i = cxListMutIterator(log_dup_list); WSBool finished = 0; cx_foreach(LogDup *, dup, i) { if(finished) break; @@ -416,14 +416,14 @@ } void shutdown_logging(void) { - CxIterator i = cxMapIteratorValues(access_log_files); + CxMapIterator i = cxMapIteratorValues(access_log_files); cx_foreach(LogFile *, log, i) { fclose(log->file); free(log); } - cxMapDestroy(access_log_files); + cxMapFree(access_log_files); if(log_dup_list) { - cxListDestroy(log_dup_list); + cxListFree(log_dup_list); } }
--- a/src/server/daemon/resourcepool.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/resourcepool.c Sun Mar 02 18:10:52 2025 +0100 @@ -44,7 +44,7 @@ } void resource_pool_cleanup(void) { - cxMapDestroy(resource_pool_types); + cxMapFree(resource_pool_types); } int resourcepool_register_type(const char *type_name, ResourceType *type_info) { @@ -231,7 +231,8 @@ if(nsapi_rq && !nsapi_rq->finished) { // request processing still ongoing and SAFs will be executed // TODO: future ucx cxMapRemove returns, if it actually removed something, therefore use just cxMapRemove - if(!cxMapRemoveAndGet(nsapi_rq->resources, cx_hash_key_str(respool->name))) { + void *value; + if(!cxMapRemoveAndGet(nsapi_rq->resources, cx_hash_key_str(respool->name), &value)) { log_ereport(LOG_FAILURE, "resourcepool_free: cannot remove resource from request: potential double free"); } }
--- a/src/server/daemon/threadpools.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/daemon/threadpools.c Sun Mar 02 18:10:52 2025 +0100 @@ -150,7 +150,7 @@ void shutdown_threadpools(int timeout) { log_ereport(LOG_INFORM, "shutdown threadpools"); - CxIterator i = cxMapIteratorValues(thread_pool_map); + CxMapIterator i = cxMapIteratorValues(thread_pool_map); cx_foreach(threadpool_t*, tp, i) { threadpool_shutdown(tp, timeout); } @@ -158,6 +158,6 @@ cx_foreach(threadpool_t*, tp, i) { threadpool_shutdown(tp, timeout); } - cxMapDestroy(thread_pool_map); - cxMapDestroy(io_pool_map); + cxMapFree(thread_pool_map); + cxMapFree(io_pool_map); }
--- a/src/server/plugins/postgresql/config.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/plugins/postgresql/config.c Sun Mar 02 18:10:52 2025 +0100 @@ -260,7 +260,7 @@ // convert parserData if(!ret) { - size_t ntables = parserData.tables->size; + size_t ntables = cxListSize(parserData.tables); repo->ntables = ntables; repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); if(repo->tables) { @@ -276,8 +276,8 @@ } // cleanup parser - cxListDestroy(parserData.tables); - cxMapDestroy(parserData.table_lookup); + cxListFree(parserData.tables); + cxMapFree(parserData.table_lookup); return ret; } @@ -357,7 +357,7 @@ PgExtTable exttable; exttable.table = tabname; exttable.isused = 0; // not relevant in config - int tableindex = (int)ext->tables->size; + int tableindex = (int)cxListSize(ext->tables); cxListAdd(ext->tables, &exttable); if(cxMapPut(ext->table_lookup, cx_hash_key_str(table), (void*)table)) {
--- a/src/server/plugins/postgresql/pgtest.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/plugins/postgresql/pgtest.c Sun Mar 02 18:10:52 2025 +0100 @@ -202,7 +202,7 @@ return NULL; } - CxMempool *mp = cxBasicMempoolCreate(64); + CxMempool *mp = cxMempoolCreateSimple(64); TestMultistatus *ms = cxMalloc(mp->allocator, sizeof(TestMultistatus)); ms->doc = doc; ms->mp = mp; @@ -227,7 +227,7 @@ void test_multistatus_destroy(TestMultistatus *ms) { if(!ms) return; xmlFreeDoc(ms->doc); - cxMempoolDestroy(ms->mp); + cxMempoolFree(ms->mp); } @@ -647,7 +647,7 @@ TestResponse *r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 1, "propfind1: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 1, "propfind1: wrong response count"); TestProperty *p = MAP_GET(r1->properties, "DAV:resourcetype"); UCX_TEST_ASSERT(p, "propfind1: missing property 'resourcetype'"); @@ -679,7 +679,7 @@ r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 5, "propfind2: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 5, "propfind2: wrong response count"); r1 = MAP_GET(ms->responses, "/propfind/res2"); UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res2 response"); @@ -713,7 +713,7 @@ r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 6, "propfind3: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 6, "propfind3: wrong response count"); r1 = MAP_GET(ms->responses, "/propfind/res1"); @@ -775,7 +775,7 @@ r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 1, "propfind1: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 1, "propfind1: wrong response count"); p = MAP_GET(r1->properties, "DAV:resourcetype"); UCX_TEST_ASSERT(r1, "propfind1: missing resourcetype property"); @@ -800,7 +800,7 @@ r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 5, "propfind2: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 5, "propfind2: wrong response count"); r1 = MAP_GET(ms->responses, "/propfind/res1"); UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res1 response"); @@ -834,7 +834,7 @@ r1 = MAP_GET(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response"); - UCX_TEST_ASSERT(ms->responses->size == 6, "propfind3: wrong response count"); + UCX_TEST_ASSERT(cxMapSize(ms->responses) == 6, "propfind3: wrong response count"); r1 = MAP_GET(ms->responses, "/propfind/res1"); UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/res1 response");
--- a/src/server/plugins/postgresql/webdav.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/plugins/postgresql/webdav.c Sun Mar 02 18:10:52 2025 +0100 @@ -444,13 +444,13 @@ ext = NULL; numext = 0; } else { - numext = pgdav->repository->prop_ext->size; + numext = cxMapSize(pgdav->repository->prop_ext); ext = pool_calloc(rq->sn->pool, numext, sizeof(PgPropfindExtCol)); if(rq->allprop) { // the map pgdav->repository->prop_ext contains all property extensions // we can just convert the map to an array - CxIterator i = cxMapIteratorValues(pgdav->repository->prop_ext); + CxMapIterator i = cxMapIteratorValues(pgdav->repository->prop_ext); int j = 0; cx_foreach(PgPropertyStoreExt *, cfg_ext, i) { PgPropfindExtCol extcol; @@ -609,7 +609,7 @@ } } - cxMapDestroy(fieldmap); + cxMapFree(fieldmap); } return 0;
--- a/src/server/safs/cgi.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/safs/cgi.c Sun Mar 02 18:10:52 2025 +0100 @@ -848,7 +848,9 @@ } value = cx_mutstrn(buf + value_begin, i - value_begin); - cx_strlower(name); + for(int l=0;i<name.length;l++) { + name.ptr[l] = tolower(name.ptr[l]); + } name = cx_strdup_a(a, cx_strtrim((cxstring){name.ptr, name.length})); value = cx_strtrim_m(value);
--- a/src/server/safs/common.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/safs/common.c Sun Mar 02 18:10:52 2025 +0100 @@ -75,7 +75,7 @@ #define COMMONSAF_RET_ERROR -2 static void common_saf_cleanup(void *d) { - cxMapDestroy(var_names); + cxMapFree(var_names); } void common_saf_init() {
--- a/src/server/test/object.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/test/object.c Sun Mar 02 18:10:52 2025 +0100 @@ -536,7 +536,7 @@ size_t pos = 0; NSAPIExpression *expr = expr_parse_logical_expr(pool, tokens, &pos); - UCX_TEST_ASSERT(pos == tokens->size, "wrong token pos"); + UCX_TEST_ASSERT(pos == cxListSize(tokens), "wrong token pos"); UCX_TEST_ASSERT(expr, "expression is null"); UCX_TEST_ASSERT(expr->type == NSAPI_EXPRESSION_IDENTIFIER, "wrong expression type"); UCX_TEST_ASSERT(expr->operator == NSAPI_EXPRESSION_CALL, "wrong expression operator"); @@ -579,7 +579,7 @@ size_t pos = 0; NSAPIExpression *expr = expr_parse_logical_expr(pool, tokens, &pos); - UCX_TEST_ASSERT(pos == tokens->size, "wrong token pos"); + UCX_TEST_ASSERT(pos == cxListSize(tokens), "wrong token pos"); UCX_TEST_ASSERT(expr, "expression is null"); UCX_TEST_ASSERT(expr->type == NSAPI_EXPRESSION_IDENTIFIER, "wrong expression type"); UCX_TEST_ASSERT(expr->operator == NSAPI_EXPRESSION_CALL, "wrong expression operator"); @@ -624,7 +624,7 @@ size_t pos = 0; NSAPIExpression *expr = expr_parse_logical_expr(pool, tokens, &pos); - UCX_TEST_ASSERT(pos == tokens->size, "wrong token pos"); + UCX_TEST_ASSERT(pos == cxListSize(tokens), "wrong token pos"); UCX_TEST_ASSERT(expr, "expression is null"); UCX_TEST_ASSERT(expr->type == NSAPI_EXPRESSION_IDENTIFIER, "wrong expression type"); UCX_TEST_ASSERT(expr->operator == NSAPI_EXPRESSION_CALL, "wrong expression operator"); @@ -680,7 +680,7 @@ size_t pos = 0; NSAPIExpression *expr = expr_parse_logical_expr(pool, tokens, &pos); - UCX_TEST_ASSERT(pos == tokens->size, "wrong token pos"); + UCX_TEST_ASSERT(pos == cxListSize(tokens), "wrong token pos"); UCX_TEST_ASSERT(expr, "expression is null"); UCX_TEST_ASSERT(expr->type == NSAPI_EXPRESSION_IDENTIFIER, "wrong expression type"); UCX_TEST_ASSERT(expr->operator == NSAPI_EXPRESSION_CALL, "wrong expression operator");
--- a/src/server/test/vfs.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/test/vfs.c Sun Mar 02 18:10:52 2025 +0100 @@ -56,7 +56,7 @@ typedef struct TestVFSDir { VFSDir dir; TestVFSFile *file; - CxIterator i; + CxMapIterator i; cxmutstr name; } TestVFSDir; @@ -321,7 +321,7 @@ return 1; } - CxIterator i = cxMapIteratorValues(vfs->files); + CxMapIterator i = cxMapIteratorValues(vfs->files); cx_foreach(TestVFSFile *, f, i) { if(f->path.length > dir->path.length && cx_strprefix(cx_strcast(f->path), cx_strcast(dir->path))){ return 1; // dir not empty @@ -454,13 +454,13 @@ cxMapPut(files, cx_hash_key_str(entry.name), dir); } - UCX_TEST_ASSERT(files->size == 4, "wrong files count"); + UCX_TEST_ASSERT(cxMapSize(files)== 4, "wrong files count"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file1")), "file1 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file2")), "file2 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file3")), "file3 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file4")), "file4 missing"); - cxMapDestroy(files); + cxMapFree(files); UCX_TEST_END;
--- a/src/server/util/object.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/util/object.c Sun Mar 02 18:10:52 2025 +0100 @@ -115,7 +115,7 @@ Expression* condition_create(pool_handle_t *pool, CxList *tokens) { size_t pos = 0; NSAPIExpression *expression = expr_parse_logical_expr(pool, tokens, &pos); - if(!expression || pos != tokens->size) { + if(!expression || pos != cxListSize(tokens)) { return NULL; } @@ -271,11 +271,11 @@ NSAPIExpression* expr_parser_pop(ExprParser *parser) { CxList *stack = parser->ex_stack; - if(stack->size == 0) { + if(cxListSize(stack)== 0) { return NULL; } - NSAPIExpression *ret = *((NSAPIExpression**)cxListAt(stack, stack->size-1)); - cxListRemove(stack, stack->size-1); + NSAPIExpression *ret = *((NSAPIExpression**)cxListAt(stack, cxListSize(stack)-1)); + cxListRemove(stack, cxListSize(stack)-1); return ret; } @@ -339,11 +339,11 @@ exp->right = NULL; exp->value.str = func->identifier; - if(parser->ex_stack->size > 0) { - NSAPIExpression *top = *((NSAPIExpression**)cxListAt(parser->ex_stack, parser->ex_stack->size - 1)); + if(cxListSize(parser->ex_stack) > 0) { + NSAPIExpression *top = *((NSAPIExpression**)cxListAt(parser->ex_stack, cxListSize(parser->ex_stack) - 1)); if(top && top->operator == NSAPI_EXPRESSION_ARG) { exp->left = top; - cxListRemove(parser->ex_stack, parser->ex_stack->size - 1); + cxListRemove(parser->ex_stack, cxListSize(parser->ex_stack) - 1); } } @@ -418,8 +418,8 @@ new_op.identifier = (cxstring){NULL,0}; new_op.expect_value = parser->expect_value; new_op.open_parenthesis = FALSE; - while(op_stack->size > 0) { - ExprOpStackItem *stack_item = cxListAt(op_stack, op_stack->size-1); + while(cxListSize(op_stack) > 0) { + ExprOpStackItem *stack_item = cxListAt(op_stack, cxListSize(op_stack)-1); if(stack_item->operator == NSAPI_EXPRESSION_NOOP) { break; } @@ -430,15 +430,15 @@ if(expr_add_operator(parser, stack_item)) { return NULL; } - cxListRemove(op_stack, op_stack->size-1); + cxListRemove(op_stack, cxListSize(op_stack)-1); } cxListAdd(op_stack, &new_op); parser->expect_value = TRUE; parser->expect_arg = FALSE; } else if(token.length == 1 && token.ptr[0] == '(') { ExprOpStackItem *prev_op = NULL; - if(op_stack->size > 0) { - prev_op = cxListAt(op_stack, op_stack->size - 1); + if(cxListSize(op_stack) > 0) { + prev_op = cxListAt(op_stack, cxListSize(op_stack) - 1); } if(prev_op && prev_op->operator == NSAPI_EXPRESSION_CALL) { @@ -459,10 +459,10 @@ } else if(token.length == 1 && token.ptr[0] == ')') { int found_open_bracket = FALSE; ExprOpStackItem stack_item; - while(op_stack->size > 0) { - ExprOpStackItem *stack_item_ptr = cxListAt(op_stack, op_stack->size-1); + while(cxListSize(op_stack) > 0) { + ExprOpStackItem *stack_item_ptr = cxListAt(op_stack, cxListSize(op_stack)-1); stack_item = *stack_item_ptr; - cxListRemove(op_stack, op_stack->size-1); + cxListRemove(op_stack, cxListSize(op_stack)-1); if(stack_item.open_parenthesis) { found_open_bracket = TRUE; break; @@ -498,18 +498,18 @@ } } - while(op_stack->size > 0) { - ExprOpStackItem *stack_item = cxListAt(op_stack, op_stack->size-1); + while(cxListSize(op_stack) > 0) { + ExprOpStackItem *stack_item = cxListAt(op_stack, cxListSize(op_stack)-1); if(stack_item->open_parenthesis) { return NULL; } if(expr_add_operator(parser, stack_item)) { return NULL; } - cxListRemove(op_stack, op_stack->size-1); + cxListRemove(op_stack, cxListSize(op_stack)-1); } - if(ex_stack->size != 1) { + if(cxListSize(ex_stack) != 1) { return NULL; } @@ -531,8 +531,8 @@ parser.expect_arg = FALSE; NSAPIExpression *ret = expr_parse_expr(&parser); - cxListDestroy(op_stack); - cxListDestroy(ex_stack); + cxListFree(op_stack); + cxListFree(ex_stack); return ret; }
--- a/src/server/webdav/multistatus.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/webdav/multistatus.c Sun Mar 02 18:10:52 2025 +0100 @@ -70,7 +70,7 @@ // write the namespaces definitions // key is the namespace prefix // the map always contains the "DAV:" namespace with the prefix "D" - CxIterator i = cxMapIterator(ms->namespaces); + CxMapIterator i = cxMapIterator(ms->namespaces); cx_foreach(CxMapEntry*, entry, i) { WSNamespace *ns = entry->value; writer_put_lit(out, " xmlns:"); @@ -717,7 +717,7 @@ } // we don't need the properties anymore - cxMapDestroy(response->properties); + cxMapFree(response->properties); response->resource.isclosed = TRUE; return ret;
--- a/src/server/webdav/requestparser.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/webdav/requestparser.c Sun Mar 02 18:10:52 2025 +0100 @@ -194,7 +194,7 @@ node = node->next; } - cxMapDestroy(propmap); // no allocated content must be freed + cxMapFree(propmap); // no allocated content must be freed if(ret) { // parse_prop failed @@ -357,7 +357,7 @@ node = node->next; } - cxMapDestroy(propmap); // allocated content must not be freed + cxMapFree(propmap); // allocated content must not be freed if(set_count + remove_count == 0) { *error = PROPPATCH_PARSER_NO_PROPERTIES;
--- a/src/server/webdav/webdav.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/webdav/webdav.c Sun Mar 02 18:10:52 2025 +0100 @@ -78,8 +78,8 @@ #define WEBDAV_RESOURCE_TYPE_COLLECTION "<D:collection/>" static void webdav_cleanup(void *data) { - cxMapDestroy(webdav_type_map); - cxMapDestroy(method_handler_map); + cxMapFree(webdav_type_map); + cxMapFree(method_handler_map); } static WebdavBackend* default_backend_create(Session *sn, Request *rq, pblock *pb, void *initData) {
--- a/src/server/webdav/xattrbackend.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/webdav/xattrbackend.c Sun Mar 02 18:10:52 2025 +0100 @@ -240,7 +240,7 @@ CxAllocator *a, CxMap *pmap) { - CxIterator i = cxMapIteratorValues(pmap); + CxMapIterator i = cxMapIteratorValues(pmap); cx_foreach(WebdavProperty*, prop, i) { if(request->propname) { prop->vtype = WS_VALUE_NO_TYPE; @@ -322,10 +322,11 @@ (const char*)prop->namespace->href, (const char*)prop->name); if(!key.data) { - cxMapDestroy(pmap); + cxMapFree(pmap); return 1; } - void *rmprop = cxMapRemoveAndGet(pmap, key); + void *rmprop = NULL; + cxMapRemoveAndGet(pmap, key, &rmprop); cxFree(a, (void*)key.data); // TODO: free rmprop @@ -345,7 +346,7 @@ } if(webdav_xattr_put_prop(pmap, prop)) { - cxMapDestroy(pmap); + cxMapFree(pmap); return 1; } @@ -506,14 +507,14 @@ int webdav_xattr_put_prop(CxMap *pmap, WebdavProperty *prop) { CxHashKey key = webdav_property_key_a( - pmap->allocator, + pmap->collection.allocator, (const char*)prop->namespace->href, (const char*)prop->name); if(!key.data) { return 1; } int ret = cxMapPut(pmap, key, prop); - cxFree(pmap->allocator, (void*)key.data); + cxFree(pmap->collection.allocator, (void*)key.data); return ret; } @@ -698,7 +699,7 @@ if(error) { // TODO: free pmap content - cxMapDestroy(pmap); + cxMapFree(pmap); pmap = NULL; } @@ -713,7 +714,7 @@ return (cxmutstr){NULL,0}; } - CxIterator i = cxMapIteratorValues(pmap); + CxMapIterator i = cxMapIteratorValues(pmap); cx_foreach(WebdavProperty*, prop, i) { WSXmlData *property_value = NULL; if(prop->vtype == WS_VALUE_XML_NODE) {
--- a/src/server/webdav/xml.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/server/webdav/xml.c Sun Mar 02 18:10:52 2025 +0100 @@ -290,7 +290,7 @@ } // convert nsmap to a list - CxIterator i = cxMapIteratorValues(nsmap); + CxMapIterator i = cxMapIteratorValues(nsmap); WSNamespace *ns; cx_foreach(WSNamespace *, ns, i) { WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList)); @@ -306,7 +306,7 @@ } } - cxMapDestroy(nsmap); + cxMapFree(nsmap); return list; }
--- a/src/tools/wstool.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/tools/wstool.c Sun Mar 02 18:10:52 2025 +0100 @@ -67,14 +67,14 @@ fprintf(stderr, "Error: No Runtime element in %s\n", configfile); return -1; } - if(list->size != 1) { + if(cxListSize(list) != 1) { fprintf(stderr, "Error: Multiple Runtime elements in %s\n", configfile); return -1; } ConfigNode *runtime = cxListAt(list, 0); cxstring tmp = serverconfig_object_directive_value(runtime, cx_str("Temp")); - cxListDestroy(list); + cxListFree(list); if(!tmp.ptr) { fprintf(stderr, "Error: No Temp directive in Runtime Object\n");
--- a/src/ucx/Makefile Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/Makefile Sun Mar 02 18:10:52 2025 +0100 @@ -36,13 +36,18 @@ SRC += buffer.c SRC += hash_key.c SRC += hash_map.c +SRC += map.c SRC += linked_list.c SRC += array_list.c SRC += list.c SRC += string.c -SRC += utils.c SRC += printf.c SRC += compare.c +SRC += tree.c +SRC += streams.c +SRC += json.c +SRC += properties.c +SRC += iterator.c OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/ucx/%$(OBJ_EXT))
--- a/src/ucx/allocator.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/allocator.c Sun Mar 02 18:10:52 2025 +0100 @@ -28,35 +28,33 @@ #include "cx/allocator.h" -__attribute__((__malloc__, __alloc_size__(2))) +#include <errno.h> + static void *cx_malloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, size_t n ) { return malloc(n); } -__attribute__((__warn_unused_result__, __alloc_size__(3))) static void *cx_realloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem, size_t n ) { return realloc(mem, n); } -__attribute__((__malloc__, __alloc_size__(2, 3))) static void *cx_calloc_stdlib( - __attribute__((__unused__)) void *d, - size_t nelem, - size_t n + cx_attr_unused void *d, + size_t nmemb, + size_t size ) { - return calloc(nelem, n); + return calloc(nmemb, size); } -__attribute__((__nonnull__)) static void cx_free_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem ) { free(mem); @@ -73,47 +71,96 @@ &cx_default_allocator_class, NULL }; -CxAllocator *cxDefaultAllocator = &cx_default_allocator; +const CxAllocator * const cxDefaultAllocator = &cx_default_allocator; - -int cx_reallocate( +int cx_reallocate_( void **mem, size_t n ) { void *nmem = realloc(*mem, n); if (nmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } else { *mem = nmem; return 0; } } +int cx_reallocatearray_( + void **mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return 1; + } else { + void *nmem = realloc(*mem, n); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } + } +} + // IMPLEMENTATION OF HIGH LEVEL API void *cxMalloc( - CxAllocator const *allocator, + const CxAllocator *allocator, size_t n ) { return allocator->cl->malloc(allocator->data, n); } void *cxRealloc( - CxAllocator const *allocator, + const CxAllocator *allocator, void *mem, size_t n ) { return allocator->cl->realloc(allocator->data, mem, n); } -int cxReallocate( - CxAllocator const *allocator, +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return NULL; + } else { + return allocator->cl->realloc(allocator->data, mem, n); + } +} + +int cxReallocate_( + const CxAllocator *allocator, void **mem, size_t n ) { void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } +} + +int cxReallocateArray_( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +) { + void *nmem = cxReallocArray(allocator, *mem, nmemb, size); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE } else { *mem = nmem; return 0; @@ -121,15 +168,15 @@ } void *cxCalloc( - CxAllocator const *allocator, - size_t nelem, - size_t n + const CxAllocator *allocator, + size_t nmemb, + size_t size ) { - return allocator->cl->calloc(allocator->data, nelem, n); + return allocator->cl->calloc(allocator->data, nmemb, size); } void cxFree( - CxAllocator const *allocator, + const CxAllocator *allocator, void *mem ) { allocator->cl->free(allocator->data, mem);
--- a/src/ucx/array_list.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/array_list.c Sun Mar 02 18:10:52 2025 +0100 @@ -27,57 +27,269 @@ */ #include "cx/array_list.h" +#include "cx/compare.h" #include <assert.h> #include <string.h> +#include <errno.h> + +// Default array reallocator + +static void *cx_array_default_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + return realloc(array, n); +} + +CxArrayReallocator cx_array_default_reallocator_impl = { + cx_array_default_realloc, NULL, NULL, 0, 0 +}; + +CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; + +// Stack-aware array reallocator + +static void *cx_array_advanced_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + // check for overflow + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + + // retrieve the pointer to the actual allocator + const CxAllocator *al = alloc->ptr1; + + // check if the array is still located on the stack + void *newmem; + if (array == alloc->ptr2) { + newmem = cxMalloc(al, n); + if (newmem != NULL && array != NULL) { + memcpy(newmem, array, n); + } + } else { + newmem = cxRealloc(al, array, n); + } + return newmem; +} + +struct cx_array_reallocator_s cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + return (struct cx_array_reallocator_s) { + cx_array_advanced_realloc, + (void*) allocator, (void*) stackmem, + 0, 0 + }; +} // LOW LEVEL ARRAY LIST FUNCTIONS -enum cx_array_copy_result cx_array_copy( - void **target, - size_t *size, - size_t *capacity, - size_t index, - void const *src, +static size_t cx_array_align_capacity( + size_t cap, + size_t alignment, + size_t max +) { + if (cap > max - alignment) { + return cap; + } else { + return cap - (cap % alignment) + alignment; + } +} + +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator +) { + // assert pointers + assert(array != NULL); + assert(size != NULL); + assert(capacity != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*array != NULL || oldcap == 0); + + // check for overflow + if (elem_count > max_size - oldsize) { + errno = EOVERFLOW; + return 1; + } + + // determine new capacity + size_t newcap = oldsize + elem_count; + + // reallocate if possible + if (newcap > oldcap) { + // calculate new capacity (next number divisible by 16) + newcap = cx_array_align_capacity(newcap, 16, max_size); + + // perform reallocation + void *newmem = reallocator->realloc( + *array, newcap, elem_size, reallocator + ); + if (newmem == NULL) { + return 1; // LCOV_EXCL_LINE + } + + // store new pointer + *array = newmem; + + // store new capacity + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + } +#endif + } + + return 0; +} + +int cx_array_copy( + void **target, + void *size, + void *capacity, + unsigned width, + size_t index, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator ) { // assert pointers assert(target != NULL); assert(size != NULL); + assert(capacity != NULL); assert(src != NULL); - // determine capacity - size_t cap = capacity == NULL ? *size : *capacity; + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*target != NULL || oldcap == 0); + + // check for overflow + if (index > max_size || elem_count > max_size - index) { + errno = EOVERFLOW; + return 1; + } // check if resize is required size_t minsize = index + elem_count; - size_t newsize = *size < minsize ? minsize : *size; - bool needrealloc = newsize > cap; + size_t newsize = oldsize < minsize ? minsize : oldsize; // reallocate if possible - if (needrealloc) { - // a reallocator and a capacity variable must be available - if (reallocator == NULL || capacity == NULL) { - return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED; - } - + size_t newcap = oldcap; + if (newsize > oldcap) { // check, if we need to repair the src pointer uintptr_t targetaddr = (uintptr_t) *target; uintptr_t srcaddr = (uintptr_t) src; bool repairsrc = targetaddr <= srcaddr - && srcaddr < targetaddr + cap * elem_size; + && srcaddr < targetaddr + oldcap * elem_size; // calculate new capacity (next number divisible by 16) - cap = newsize - (newsize % 16) + 16; - assert(cap > newsize); + newcap = cx_array_align_capacity(newsize, 16, max_size); + assert(newcap > newsize); // perform reallocation void *newmem = reallocator->realloc( - *target, cap, elem_size, reallocator + *target, newcap, elem_size, reallocator ); if (newmem == NULL) { - return CX_ARRAY_COPY_REALLOC_FAILED; + return 1; } // repair src pointer, if necessary @@ -85,9 +297,8 @@ src = ((char *) newmem) + (srcaddr - targetaddr); } - // store new pointer and capacity + // store new pointer *target = newmem; - *capacity = cap; } // determine target pointer @@ -95,16 +306,255 @@ start += index * elem_size; // copy elements and set new size + // note: no overflow check here, b/c we cannot get here w/o allocation memmove(start, src, elem_count * elem_size); - *size = newsize; + + // if any of size or capacity changed, store them back + if (newsize != oldsize || newcap != oldcap) { + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + *(size_t*) size = newsize; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + *(uint16_t*) size = (uint16_t) newsize; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + *(uint8_t*) size = (uint8_t) newsize; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + *(uint32_t*) size = (uint32_t) newsize; + } +#endif + } // return successfully - return CX_ARRAY_COPY_SUCCESS; + return 0; +} + +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + // assert pointers + assert(target != NULL); + assert(size != NULL); + assert(capacity != NULL); + assert(cmp_func != NULL); + assert(sorted_data != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // corner case + if (elem_count == 0) return 0; + + // overflow check + if (elem_count > SIZE_MAX - *size) { + errno = EOVERFLOW; + return 1; + } + + // store some counts + size_t old_size = *size; + size_t needed_capacity = old_size + elem_count; + + // if we need more than we have, try a reallocation + if (needed_capacity > *capacity) { + size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); + void *new_mem = reallocator->realloc( + *target, new_capacity, elem_size, reallocator + ); + if (new_mem == NULL) { + // give it up right away, there is no contract + // that requires us to insert as much as we can + return 1; // LCOV_EXCL_LINE + } + *target = new_mem; + *capacity = new_capacity; + } + + // now we have guaranteed that we can insert everything + size_t new_size = old_size + elem_count; + *size = new_size; + + // declare the source and destination indices/pointers + size_t si = 0, di = 0; + const char *src = sorted_data; + char *dest = *target; + + // find the first insertion point + di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func); + dest += di * elem_size; + + // move the remaining elements in the array completely to the right + // we will call it the "buffer" for parked elements + size_t buf_size = old_size - di; + size_t bi = new_size - buf_size; + char *bptr = ((char *) *target) + bi * elem_size; + memmove(bptr, dest, buf_size * elem_size); + + // while there are both source and buffered elements left, + // copy them interleaving + while (si < elem_count && bi < new_size) { + // determine how many source elements can be inserted + size_t copy_len, bytes_copied; + copy_len = cx_array_binary_search_sup( + src, + elem_count - si, + elem_size, + bptr, + cmp_func + ); + + // copy the source elements + bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied; + si += copy_len; + + // when all source elements are in place, we are done + if (si >= elem_count) break; + + // determine how many buffered elements need to be restored + copy_len = cx_array_binary_search_sup( + bptr, + new_size - bi, + elem_size, + src, + cmp_func + ); + + // restore the buffered elements + bytes_copied = copy_len * elem_size; + memmove(dest, bptr, bytes_copied); + dest += bytes_copied; + bptr += bytes_copied; + bi += copy_len; + } + + // still source elements left? simply append them + if (si < elem_count) { + memcpy(dest, src, elem_size * (elem_count - si)); + } + + // still buffer elements left? + // don't worry, we already moved them to the correct place + + return 0; +} + +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + // special case: empty array + if (size == 0) return 0; + + // declare a variable that will contain the compare results + int result; + + // cast the array pointer to something we can use offsets with + const char *array = arr; + + // check the first array element + result = cmp_func(elem, array); + if (result < 0) { + return size; + } else if (result == 0) { + return 0; + } + + // special case: there is only one element and that is smaller + if (size == 1) return 0; + + // check the last array element + result = cmp_func(elem, array + elem_size * (size - 1)); + if (result >= 0) { + return size - 1; + } + + // the element is now guaranteed to be somewhere in the list + // so start the binary search + size_t left_index = 1; + size_t right_index = size - 1; + size_t pivot_index; + + while (left_index <= right_index) { + pivot_index = left_index + (right_index - left_index) / 2; + const char *arr_elem = array + pivot_index * elem_size; + result = cmp_func(elem, arr_elem); + if (result == 0) { + // found it! + return pivot_index; + } else if (result < 0) { + // element is smaller than pivot, continue search left + right_index = pivot_index - 1; + } else { + // element is larger than pivot, continue search right + left_index = pivot_index + 1; + } + } + + // report the largest upper bound + return result < 0 ? (pivot_index - 1) : pivot_index; +} + +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t index = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (index < size && + cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { + return index; + } else { + return size; + } +} + +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t inf = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (inf == size) { + // no infimum means, first element is supremum + return 0; + } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { + return inf; + } else { + return inf + 1; + } } #ifndef CX_ARRAY_SWAP_SBO_SIZE #define CX_ARRAY_SWAP_SBO_SIZE 128 #endif +const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; void cx_array_swap( void *arr, @@ -151,70 +601,58 @@ struct cx_list_s base; void *data; size_t capacity; - struct cx_array_reallocator_s reallocator; + CxArrayReallocator reallocator; } cx_array_list; -static void *cx_arl_realloc( - void *array, - size_t capacity, - size_t elem_size, - struct cx_array_reallocator_s *alloc -) { - // retrieve the pointer to the list allocator - CxAllocator const *al = alloc->ptr1; - - // use the list allocator to reallocate the memory - return cxRealloc(al, array, capacity * elem_size); -} - static void cx_arl_destructor(struct cx_list_s *list) { cx_array_list *arl = (cx_array_list *) list; char *ptr = arl->data; - if (list->simple_destructor) { - for (size_t i = 0; i < list->size; i++) { + if (list->collection.simple_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { cx_invoke_simple_destructor(list, ptr); - ptr += list->item_size; + ptr += list->collection.elem_size; } } - if (list->advanced_destructor) { - for (size_t i = 0; i < list->size; i++) { + if (list->collection.advanced_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { cx_invoke_advanced_destructor(list, ptr); - ptr += list->item_size; + ptr += list->collection.elem_size; } } - cxFree(list->allocator, arl->data); - cxFree(list->allocator, list); + cxFree(list->collection.allocator, arl->data); + cxFree(list->collection.allocator, list); } static size_t cx_arl_insert_array( struct cx_list_s *list, size_t index, - void const *array, + const void *array, size_t n ) { // out of bounds and special case check - if (index > list->size || n == 0) return 0; + if (index > list->collection.size || n == 0) return 0; // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; // do we need to move some elements? - if (index < list->size) { - char const *first_to_move = (char const *) arl->data; - first_to_move += index * list->item_size; - size_t elems_to_move = list->size - index; + if (index < list->collection.size) { + const char *first_to_move = (const char *) arl->data; + first_to_move += index * list->collection.elem_size; + size_t elems_to_move = list->collection.size - index; size_t start_of_moved = index + n; - if (CX_ARRAY_COPY_SUCCESS != cx_array_copy( + if (cx_array_copy( &arl->data, - &list->size, + &list->collection.size, &arl->capacity, + 0, start_of_moved, first_to_move, - list->item_size, + list->collection.elem_size, elems_to_move, &arl->reallocator )) { @@ -228,114 +666,170 @@ // therefore, it is impossible to leave this function with an invalid array // place the new elements - if (CX_ARRAY_COPY_SUCCESS == cx_array_copy( + if (cx_array_copy( &arl->data, - &list->size, + &list->collection.size, &arl->capacity, + 0, index, array, - list->item_size, + list->collection.elem_size, n, &arl->reallocator )) { - return n; - } else { // array list implementation is "all or nothing" return 0; + } else { + return n; + } +} + +static size_t cx_arl_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + if (cx_array_insert_sorted( + &arl->data, + &list->collection.size, + &arl->capacity, + list->collection.cmpfunc, + sorted_data, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; } } static int cx_arl_insert_element( struct cx_list_s *list, size_t index, - void const *element + const void *element ) { return 1 != cx_arl_insert_array(list, index, element, 1); } static int cx_arl_insert_iter( - struct cx_mut_iterator_s *iter, - void const *elem, + struct cx_iterator_s *iter, + const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle; - if (iter->index < list->size) { + struct cx_list_s *list = iter->src_handle.m; + if (iter->index < list->collection.size) { int result = cx_arl_insert_element( list, iter->index + 1 - prepend, elem ); - if (result == 0 && prepend != 0) { - iter->index++; - iter->elem_handle = ((char *) iter->elem_handle) + list->item_size; + if (result == 0) { + iter->elem_count++; + if (prepend != 0) { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; + } } return result; } else { - int result = cx_arl_insert_element(list, list->size, elem); - iter->index = list->size; + int result = cx_arl_insert_element(list, list->collection.size, elem); + if (result == 0) { + iter->elem_count++; + iter->index = list->collection.size; + } return result; } } -static int cx_arl_remove( +static size_t cx_arl_remove( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ) { cx_array_list *arl = (cx_array_list *) list; // out-of-bounds check - if (index >= list->size) { - return 1; - } - - // content destruction - cx_invoke_destructor(list, ((char *) arl->data) + index * list->item_size); - - // short-circuit removal of last element - if (index == list->size - 1) { - list->size--; - return 0; + size_t remove; + if (index >= list->collection.size) { + remove = 0; + } else if (index + num > list->collection.size) { + remove = list->collection.size - index; + } else { + remove = num; } - // just move the elements starting at index to the left - int result = cx_array_copy( + // easy exit + if (remove == 0) return 0; + + // destroy or copy contents + if (targetbuf == NULL) { + for (size_t idx = index; idx < index + remove; idx++) { + cx_invoke_destructor( + list, + ((char *) arl->data) + idx * list->collection.elem_size + ); + } + } else { + memcpy( + targetbuf, + ((char *) arl->data) + index * list->collection.elem_size, + remove * list->collection.elem_size + ); + } + + // short-circuit removal of last elements + if (index + remove == list->collection.size) { + list->collection.size -= remove; + return remove; + } + + // just move the elements to the left + cx_array_copy( &arl->data, - &list->size, + &list->collection.size, &arl->capacity, + 0, index, - ((char *) arl->data) + (index + 1) * list->item_size, - list->item_size, - list->size - index - 1, + ((char *) arl->data) + (index + remove) * list->collection.elem_size, + list->collection.elem_size, + list->collection.size - index - remove, &arl->reallocator ); - if (result == 0) { - // decrease the size - list->size--; - } - return result; + + // decrease the size + list->collection.size -= remove; + + return remove; } static void cx_arl_clear(struct cx_list_s *list) { - if (list->size == 0) return; + if (list->collection.size == 0) return; cx_array_list *arl = (cx_array_list *) list; char *ptr = arl->data; - if (list->simple_destructor) { - for (size_t i = 0; i < list->size; i++) { + if (list->collection.simple_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { cx_invoke_simple_destructor(list, ptr); - ptr += list->item_size; + ptr += list->collection.elem_size; } } - if (list->advanced_destructor) { - for (size_t i = 0; i < list->size; i++) { + if (list->collection.advanced_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { cx_invoke_advanced_destructor(list, ptr); - ptr += list->item_size; + ptr += list->collection.elem_size; } } - memset(arl->data, 0, list->size * list->item_size); - list->size = 0; + memset(arl->data, 0, list->collection.size * list->collection.elem_size); + list->collection.size = 0; } static int cx_arl_swap( @@ -343,148 +837,157 @@ size_t i, size_t j ) { - if (i >= list->size || j >= list->size) return 1; + if (i >= list->collection.size || j >= list->collection.size) return 1; cx_array_list *arl = (cx_array_list *) list; - cx_array_swap(arl->data, list->item_size, i, j); + cx_array_swap(arl->data, list->collection.elem_size, i, j); return 0; } static void *cx_arl_at( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index ) { - if (index < list->size) { - cx_array_list const *arl = (cx_array_list const *) list; + if (index < list->collection.size) { + const cx_array_list *arl = (const cx_array_list *) list; char *space = arl->data; - return space + index * list->item_size; + return space + index * list->collection.elem_size; } else { return NULL; } } -static ssize_t cx_arl_find( - struct cx_list_s const *list, - void const *elem +static size_t cx_arl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove ) { - assert(list->cmpfunc != NULL); - assert(list->size < SIZE_MAX / 2); - char *cur = ((cx_array_list const *) list)->data; + assert(list != NULL); + assert(list->collection.cmpfunc != NULL); + if (list->collection.size == 0) return 0; + char *cur = ((const cx_array_list *) list)->data; - for (ssize_t i = 0; i < (ssize_t) list->size; i++) { - if (0 == list->cmpfunc(elem, cur)) { + // optimize with binary search, when sorted + if (list->collection.sorted) { + size_t i = cx_array_binary_search( + cur, + list->collection.size, + list->collection.elem_size, + elem, + list->collection.cmpfunc + ); + if (remove && i < list->collection.size) { + cx_arl_remove(list, i, 1, NULL); + } + return i; + } + + // fallback: linear search + for (size_t i = 0; i < list->collection.size; i++) { + if (0 == list->collection.cmpfunc(elem, cur)) { + if (remove) { + cx_arl_remove(list, i, 1, NULL); + } return i; } - cur += list->item_size; + cur += list->collection.elem_size; } - - return -1; + return list->collection.size; } static void cx_arl_sort(struct cx_list_s *list) { - assert(list->cmpfunc != NULL); + assert(list->collection.cmpfunc != NULL); qsort(((cx_array_list *) list)->data, - list->size, - list->item_size, - list->cmpfunc + list->collection.size, + list->collection.elem_size, + list->collection.cmpfunc ); } static int cx_arl_compare( - struct cx_list_s const *list, - struct cx_list_s const *other + const struct cx_list_s *list, + const struct cx_list_s *other ) { - assert(list->cmpfunc != NULL); - if (list->size == other->size) { - char const *left = ((cx_array_list const *) list)->data; - char const *right = ((cx_array_list const *) other)->data; - for (size_t i = 0; i < list->size; i++) { - int d = list->cmpfunc(left, right); + assert(list->collection.cmpfunc != NULL); + if (list->collection.size == other->collection.size) { + const char *left = ((const cx_array_list *) list)->data; + const char *right = ((const cx_array_list *) other)->data; + for (size_t i = 0; i < list->collection.size; i++) { + int d = list->collection.cmpfunc(left, right); if (d != 0) { return d; } - left += list->item_size; - right += other->item_size; + left += list->collection.elem_size; + right += other->collection.elem_size; } return 0; } else { - return list->size < other->size ? -1 : 1; + return list->collection.size < other->collection.size ? -1 : 1; } } static void cx_arl_reverse(struct cx_list_s *list) { - if (list->size < 2) return; - void *data = ((cx_array_list const *) list)->data; - size_t half = list->size / 2; + if (list->collection.size < 2) return; + void *data = ((const cx_array_list *) list)->data; + size_t half = list->collection.size / 2; for (size_t i = 0; i < half; i++) { - cx_array_swap(data, list->item_size, i, list->size - 1 - i); + cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i); } } -static bool cx_arl_iter_valid(void const *it) { - struct cx_iterator_s const *iter = it; - struct cx_list_s const *list = iter->src_handle; - return iter->index < list->size; +static bool cx_arl_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + const struct cx_list_s *list = iter->src_handle.c; + return iter->index < list->collection.size; } -static void *cx_arl_iter_current(void const *it) { - struct cx_iterator_s const *iter = it; +static void *cx_arl_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; return iter->elem_handle; } static void cx_arl_iter_next(void *it) { - struct cx_iterator_base_s *itbase = it; - if (itbase->remove) { - struct cx_mut_iterator_s *iter = it; - itbase->remove = false; - cx_arl_remove(iter->src_handle, iter->index); + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } else { - struct cx_iterator_s *iter = it; iter->index++; iter->elem_handle = ((char *) iter->elem_handle) - + ((struct cx_list_s const *) iter->src_handle)->item_size; + + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size; } } static void cx_arl_iter_prev(void *it) { - struct cx_iterator_base_s *itbase = it; - struct cx_mut_iterator_s *iter = it; - cx_array_list *const list = iter->src_handle; - if (itbase->remove) { - itbase->remove = false; - cx_arl_remove(iter->src_handle, iter->index); + struct cx_iterator_s *iter = it; + const cx_array_list *list = iter->src_handle.c; + if (iter->base.remove) { + iter->base.remove = false; + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } iter->index--; - if (iter->index < list->base.size) { + if (iter->index < list->base.collection.size) { iter->elem_handle = ((char *) list->data) - + iter->index * list->base.item_size; + + iter->index * list->base.collection.elem_size; } } -static bool cx_arl_iter_flag_rm(void *it) { - struct cx_iterator_base_s *iter = it; - if (iter->mutating) { - iter->remove = true; - return true; - } else { - return false; - } -} static struct cx_iterator_s cx_arl_iterator( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index, bool backwards ) { struct cx_iterator_s iter; iter.index = index; - iter.src_handle = list; + iter.src_handle.c = list; iter.elem_handle = cx_arl_at(list, index); + iter.elem_size = list->collection.elem_size; + iter.elem_count = list->collection.size; iter.base.valid = cx_arl_iter_valid; iter.base.current = cx_arl_iter_current; iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next; - iter.base.flag_removal = cx_arl_iter_flag_rm; iter.base.remove = false; iter.base.mutating = false; @@ -495,12 +998,13 @@ cx_arl_destructor, cx_arl_insert_element, cx_arl_insert_array, + cx_arl_insert_sorted, cx_arl_insert_iter, cx_arl_remove, cx_arl_clear, cx_arl_swap, cx_arl_at, - cx_arl_find, + cx_arl_find_remove, cx_arl_sort, cx_arl_compare, cx_arl_reverse, @@ -508,9 +1012,9 @@ }; CxList *cxArrayListCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, cx_compare_func comparator, - size_t item_size, + size_t elem_size, size_t initial_capacity ) { if (allocator == NULL) { @@ -519,29 +1023,20 @@ cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list)); if (list == NULL) return NULL; - - list->base.cl = &cx_array_list_class; - list->base.allocator = allocator; - list->base.cmpfunc = comparator; + cx_list_init((CxList*)list, &cx_array_list_class, + allocator, comparator, elem_size); list->capacity = initial_capacity; - if (item_size > 0) { - list->base.item_size = item_size; - } else { - item_size = sizeof(void *); - cxListStorePointers((CxList *) list); - } - - // allocate the array after the real item_size is known - list->data = cxCalloc(allocator, initial_capacity, item_size); - if (list->data == NULL) { + // allocate the array after the real elem_size is known + list->data = cxCalloc(allocator, initial_capacity, + list->base.collection.elem_size); + if (list->data == NULL) { // LCOV_EXCL_START cxFree(allocator, list); return NULL; - } + } // LCOV_EXCL_STOP // configure the reallocator - list->reallocator.realloc = cx_arl_realloc; - list->reallocator.ptr1 = (void *) allocator; + list->reallocator = cx_array_reallocator(allocator, NULL); return (CxList *) list; }
--- a/src/ucx/buffer.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/buffer.c Sun Mar 02 18:10:52 2025 +0100 @@ -27,25 +27,41 @@ */ #include "cx/buffer.h" -#include "cx/utils.h" #include <stdio.h> #include <string.h> +#include <errno.h> + +static int buffer_copy_on_write(CxBuffer* buffer) { + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; + void *newspace = cxMalloc(buffer->allocator, buffer->capacity); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; +} int cxBufferInit( CxBuffer *buffer, void *space, size_t capacity, - CxAllocator const *allocator, + const CxAllocator *allocator, int flags ) { - if (allocator == NULL) allocator = cxDefaultAllocator; + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (flags & CX_BUFFER_COPY_ON_EXTEND) { + flags |= CX_BUFFER_AUTO_EXTEND; + } buffer->allocator = allocator; buffer->flags = flags; if (!space) { buffer->bytes = cxMalloc(allocator, capacity); if (buffer->bytes == NULL) { - return 1; + return -1; // LCOV_EXCL_LINE } buffer->flags |= CX_BUFFER_FREE_CONTENTS; } else { @@ -55,42 +71,55 @@ buffer->size = 0; buffer->pos = 0; - buffer->flush_func = NULL; - buffer->flush_target = NULL; - buffer->flush_blkmax = 0; - buffer->flush_blksize = 4096; - buffer->flush_threshold = SIZE_MAX; + buffer->flush = NULL; return 0; } +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config +) { + buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE + memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); + return 0; +} + void cxBufferDestroy(CxBuffer *buffer) { - if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { + if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } + free(buffer->flush); + memset(buffer, 0, sizeof(CxBuffer)); } CxBuffer *cxBufferCreate( void *space, size_t capacity, - CxAllocator const *allocator, + const CxAllocator *allocator, int flags ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); if (buf == NULL) return NULL; if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { return buf; } else { + // LCOV_EXCL_START cxFree(allocator, buf); return NULL; + // LCOV_EXCL_STOP } } void cxBufferFree(CxBuffer *buffer) { - if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { - cxFree(buffer->allocator, buffer->bytes); - } - cxFree(buffer->allocator, buffer); + if (buffer == NULL) return; + const CxAllocator *allocator = buffer->allocator; + cxBufferDestroy(buffer); + cxFree(allocator, buffer); } int cxBufferSeek( @@ -117,10 +146,11 @@ npos += offset; if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { + errno = EOVERFLOW; return -1; } - if (npos >= buffer->size) { + if (npos > buffer->size) { return -1; } else { buffer->pos = npos; @@ -130,12 +160,19 @@ } void cxBufferClear(CxBuffer *buffer) { - memset(buffer->bytes, 0, buffer->size); + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) { + memset(buffer->bytes, 0, buffer->size); + } buffer->size = 0; buffer->pos = 0; } -int cxBufferEof(CxBuffer const *buffer) { +void cxBufferReset(CxBuffer *buffer) { + buffer->size = 0; + buffer->pos = 0; +} + +bool cxBufferEof(const CxBuffer *buffer) { return buffer->pos >= buffer->size; } @@ -147,58 +184,82 @@ return 0; } - if (cxReallocate(buffer->allocator, + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + void *newspace = cxMalloc(buffer->allocator, newcap); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->capacity = newcap; + buffer->flags &= ~force_copy_flags; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; + } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { buffer->capacity = newcap; return 0; } else { - return -1; + return -1; // LCOV_EXCL_LINE } } -/** - * Helps flushing data to the flush target of a buffer. - * - * @param buffer the buffer containing the config - * @param space the data to flush - * @param size the element size - * @param nitems the number of items - * @return the number of items flushed - */ -static size_t cx_buffer_write_flush_helper( - CxBuffer *buffer, - unsigned char const *space, +static size_t cx_buffer_flush_helper( + const CxBuffer *buffer, + const unsigned char *src, size_t size, size_t nitems ) { - size_t pos = 0; - size_t remaining = nitems; - size_t max_items = buffer->flush_blksize / size; - while (remaining > 0) { - size_t items = remaining > max_items ? max_items : remaining; - size_t flushed = buffer->flush_func( - space + pos, - size, items, - buffer->flush_target); + // flush data from an arbitrary source + // does not need to be the buffer's contents + size_t max_items = buffer->flush->blksize / size; + size_t fblocks = 0; + size_t flushed_total = 0; + while (nitems > 0 && fblocks < buffer->flush->blkmax) { + fblocks++; + size_t items = nitems > max_items ? max_items : nitems; + size_t flushed = buffer->flush->wfunc( + src, size, items, buffer->flush->target); if (flushed > 0) { - pos += (flushed * size); - remaining -= flushed; + flushed_total += flushed; + src += flushed * size; + nitems -= flushed; } else { // if no bytes can be flushed out anymore, we give up break; } } - return nitems - remaining; + return flushed_total; +} + +static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { + // flush the current contents of the buffer + unsigned char *space = buffer->bytes; + size_t remaining = buffer->pos / size; + size_t flushed_total = cx_buffer_flush_helper( + buffer, space, size, remaining); + + // shift the buffer left after flushing + // IMPORTANT: up to this point, copy on write must have been + // performed already, because we can't do error handling here + cxBufferShiftLeft(buffer, flushed_total*size); + + return flushed_total; +} + +size_t cxBufferFlush(CxBuffer *buffer) { + if (buffer_copy_on_write(buffer)) return 0; + return cx_buffer_flush_impl(buffer, 1); } size_t cxBufferWrite( - void const *ptr, + const void *ptr, size_t size, size_t nitems, CxBuffer *buffer ) { // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { + if (buffer_copy_on_write(buffer)) return 0; memcpy(buffer->bytes + buffer->pos, ptr, nitems); buffer->pos += nitems; if (buffer->pos > buffer->size) { @@ -207,82 +268,89 @@ return nitems; } - size_t len; - size_t nitems_out = nitems; + size_t len, total_flushed = 0; +cx_buffer_write_retry: if (cx_szmul(size, nitems, &len)) { - return 0; + errno = EOVERFLOW; + return total_flushed; } - size_t required = buffer->pos + len; - if (buffer->pos > required) { - return 0; + if (buffer->pos > SIZE_MAX - len) { + errno = EOVERFLOW; + return total_flushed; } + size_t required = buffer->pos + len; bool perform_flush = false; if (required > buffer->capacity) { - if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { - if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + if (buffer->flush != NULL && required > buffer->flush->threshold) { perform_flush = true; } else { if (cxBufferMinimumCapacity(buffer, required)) { - return 0; + return total_flushed; // LCOV_EXCL_LINE } } } else { - if (buffer->flush_blkmax > 0) { + if (buffer->flush != NULL) { perform_flush = true; } else { - // truncate data to be written, if we can neither extend nor flush + // truncate data, if we can neither extend nor flush len = buffer->capacity - buffer->pos; if (size > 1) { len -= len % size; } - nitems_out = len / size; + nitems = len / size; } } } + // check here and not above because of possible truncation if (len == 0) { - return len; + return total_flushed; } - if (perform_flush) { - size_t flush_max; - if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { - return 0; - } - size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL - ? buffer->pos - : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); - if (flush_pos == buffer->pos) { - // entire buffer has been flushed, we can reset - buffer->size = buffer->pos = 0; - - size_t items_flush; // how many items can also be directly flushed - size_t items_keep; // how many items have to be written to the buffer + // check if we need to copy + if (buffer_copy_on_write(buffer)) return 0; - items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; - if (items_flush > 0) { - items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); - // in case we could not flush everything, keep the rest + // perform the operation + if (perform_flush) { + size_t items_flushed; + if (buffer->pos == 0) { + // if we don't have data in the buffer, but are instructed + // to flush, it means that we are supposed to relay the data + items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); + if (items_flushed == 0) { + // we needed to relay data, but could not flush anything + // i.e. we have to give up to avoid endless trying + return 0; + } + nitems -= items_flushed; + total_flushed += items_flushed; + if (nitems > 0) { + ptr = ((unsigned char*)ptr) + items_flushed * size; + goto cx_buffer_write_retry; } - items_keep = nitems - items_flush; - if (items_keep > 0) { - // try again with the remaining stuff - unsigned char const *new_ptr = ptr; - new_ptr += items_flush * size; - // report the directly flushed items as written plus the remaining stuff - return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); - } else { - // all items have been flushed - report them as written - return nitems; + return total_flushed; + } else { + items_flushed = cx_buffer_flush_impl(buffer, size); + if (items_flushed == 0) { + // flush target is full, let's try to truncate + size_t remaining_space; + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + remaining_space = buffer->flush->threshold > buffer->pos + ? buffer->flush->threshold - buffer->pos + : 0; + } else { + remaining_space = buffer->capacity > buffer->pos + ? buffer->capacity - buffer->pos + : 0; + } + nitems = remaining_space / size; + if (nitems == 0) { + return total_flushed; + } } - } else if (flush_pos == 0) { - // nothing could be flushed at all, we immediately give up without writing any data - return 0; - } else { - // we were partially successful, we shift left and try again - cxBufferShiftLeft(buffer, flush_pos); - return cxBufferWrite(ptr, size, nitems, buffer); + goto cx_buffer_write_retry; } } else { memcpy(buffer->bytes + buffer->pos, ptr, len); @@ -290,9 +358,31 @@ if (buffer->pos > buffer->size) { buffer->size = buffer->pos; } - return nitems_out; + return total_flushed + nitems; } +} +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { + size_t pos = buffer->pos; + size_t append_pos = buffer->size; + buffer->pos = append_pos; + size_t written = cxBufferWrite(ptr, size, nitems, buffer); + // the buffer might have been flushed + // we must compute a possible delta for the position + // expected: pos = append_pos + written + // -> if this is not the case, there is a delta + size_t delta = append_pos + written*size - buffer->pos; + if (delta > pos) { + buffer->pos = 0; + } else { + buffer->pos = pos - delta; + } + return written; } int cxBufferPut( @@ -308,6 +398,17 @@ } } +int cxBufferTerminate(CxBuffer *buffer) { + bool success = 0 == cxBufferPut(buffer, 0); + if (success) { + buffer->pos--; + buffer->size--; + return 0; + } else { + return -1; + } +} + size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -323,6 +424,7 @@ ) { size_t len; if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; return 0; } if (buffer->pos + len > buffer->size) { @@ -357,6 +459,7 @@ if (shift >= buffer->size) { buffer->pos = buffer->size = 0; } else { + if (buffer_copy_on_write(buffer)) return -1; memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); buffer->size -= shift; @@ -373,14 +476,18 @@ CxBuffer *buffer, size_t shift ) { + if (buffer->size > SIZE_MAX - shift) { + errno = EOVERFLOW; + return -1; + } size_t req_capacity = buffer->size + shift; size_t movebytes; // auto extend buffer, if required and enabled if (buffer->capacity < req_capacity) { - if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { if (cxBufferMinimumCapacity(buffer, req_capacity)) { - return 1; + return -1; // LCOV_EXCL_LINE } movebytes = buffer->size; } else { @@ -390,8 +497,11 @@ movebytes = buffer->size; } - memmove(buffer->bytes + shift, buffer->bytes, movebytes); - buffer->size = shift + movebytes; + if (movebytes > 0) { + if (buffer_copy_on_write(buffer)) return -1; + memmove(buffer->bytes + shift, buffer->bytes, movebytes); + buffer->size = shift + movebytes; + } buffer->pos += shift; if (buffer->pos > buffer->size) {
--- a/src/ucx/compare.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/compare.c Sun Mar 02 18:10:52 2025 +0100 @@ -30,9 +30,21 @@ #include <math.h> -int cx_cmp_int(void const *i1, void const *i2) { +int cx_vcmp_int(int a, int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_int(const void *i1, const void *i2) { int a = *((const int *) i1); int b = *((const int *) i2); + return cx_vcmp_int(a, b); +} + +int cx_vcmp_longint(long int a, long int b) { if (a == b) { return 0; } else { @@ -40,9 +52,13 @@ } } -int cx_cmp_longint(void const *i1, void const *i2) { +int cx_cmp_longint(const void *i1, const void *i2) { long int a = *((const long int *) i1); long int b = *((const long int *) i2); + return cx_vcmp_longint(a, b); +} + +int cx_vcmp_longlong(long long a, long long b) { if (a == b) { return 0; } else { @@ -50,9 +66,13 @@ } } -int cx_cmp_longlong(void const *i1, void const *i2) { +int cx_cmp_longlong(const void *i1, const void *i2) { long long a = *((const long long *) i1); long long b = *((const long long *) i2); + return cx_vcmp_longlong(a, b); +} + +int cx_vcmp_int16(int16_t a, int16_t b) { if (a == b) { return 0; } else { @@ -60,9 +80,13 @@ } } -int cx_cmp_int16(void const *i1, void const *i2) { +int cx_cmp_int16(const void *i1, const void *i2) { int16_t a = *((const int16_t *) i1); int16_t b = *((const int16_t *) i2); + return cx_vcmp_int16(a, b); +} + +int cx_vcmp_int32(int32_t a, int32_t b) { if (a == b) { return 0; } else { @@ -70,9 +94,13 @@ } } -int cx_cmp_int32(void const *i1, void const *i2) { +int cx_cmp_int32(const void *i1, const void *i2) { int32_t a = *((const int32_t *) i1); int32_t b = *((const int32_t *) i2); + return cx_vcmp_int32(a, b); +} + +int cx_vcmp_int64(int64_t a, int64_t b) { if (a == b) { return 0; } else { @@ -80,9 +108,13 @@ } } -int cx_cmp_int64(void const *i1, void const *i2) { +int cx_cmp_int64(const void *i1, const void *i2) { int64_t a = *((const int64_t *) i1); int64_t b = *((const int64_t *) i2); + return cx_vcmp_int64(a, b); +} + +int cx_vcmp_uint(unsigned int a, unsigned int b) { if (a == b) { return 0; } else { @@ -90,9 +122,13 @@ } } -int cx_cmp_uint(void const *i1, void const *i2) { +int cx_cmp_uint(const void *i1, const void *i2) { unsigned int a = *((const unsigned int *) i1); unsigned int b = *((const unsigned int *) i2); + return cx_vcmp_uint(a, b); +} + +int cx_vcmp_ulongint(unsigned long int a, unsigned long int b) { if (a == b) { return 0; } else { @@ -100,9 +136,13 @@ } } -int cx_cmp_ulongint(void const *i1, void const *i2) { +int cx_cmp_ulongint(const void *i1, const void *i2) { unsigned long int a = *((const unsigned long int *) i1); unsigned long int b = *((const unsigned long int *) i2); + return cx_vcmp_ulongint(a, b); +} + +int cx_vcmp_ulonglong(unsigned long long a, unsigned long long b) { if (a == b) { return 0; } else { @@ -110,9 +150,13 @@ } } -int cx_cmp_ulonglong(void const *i1, void const *i2) { +int cx_cmp_ulonglong(const void *i1, const void *i2) { unsigned long long a = *((const unsigned long long *) i1); unsigned long long b = *((const unsigned long long *) i2); + return cx_vcmp_ulonglong(a, b); +} + +int cx_vcmp_uint16(uint16_t a, uint16_t b) { if (a == b) { return 0; } else { @@ -120,9 +164,13 @@ } } -int cx_cmp_uint16(void const *i1, void const *i2) { +int cx_cmp_uint16(const void *i1, const void *i2) { uint16_t a = *((const uint16_t *) i1); uint16_t b = *((const uint16_t *) i2); + return cx_vcmp_uint16(a, b); +} + +int cx_vcmp_uint32(uint32_t a, uint32_t b) { if (a == b) { return 0; } else { @@ -130,9 +178,13 @@ } } -int cx_cmp_uint32(void const *i1, void const *i2) { +int cx_cmp_uint32(const void *i1, const void *i2) { uint32_t a = *((const uint32_t *) i1); uint32_t b = *((const uint32_t *) i2); + return cx_vcmp_uint32(a, b); +} + +int cx_vcmp_uint64(uint64_t a, uint64_t b) { if (a == b) { return 0; } else { @@ -140,19 +192,13 @@ } } -int cx_cmp_uint64(void const *i1, void const *i2) { +int cx_cmp_uint64(const void *i1, const void *i2) { uint64_t a = *((const uint64_t *) i1); uint64_t b = *((const uint64_t *) i2); - if (a == b) { - return 0; - } else { - return a < b ? -1 : 1; - } + return cx_vcmp_uint64(a, b); } -int cx_cmp_float(void const *f1, void const *f2) { - float a = *((const float *) f1); - float b = *((const float *) f2); +int cx_vcmp_float(float a, float b) { if (fabsf(a - b) < 1e-6f) { return 0; } else { @@ -160,12 +206,13 @@ } } -int cx_cmp_double( - void const *d1, - void const *d2 -) { - double a = *((const double *) d1); - double b = *((const double *) d2); +int cx_cmp_float(const void *f1, const void *f2) { + float a = *((const float *) f1); + float b = *((const float *) f2); + return cx_vcmp_float(a, b); +} + +int cx_vcmp_double(double a, double b) { if (fabs(a - b) < 1e-14) { return 0; } else { @@ -173,12 +220,33 @@ } } +int cx_cmp_double( + const void *d1, + const void *d2 +) { + double a = *((const double *) d1); + double b = *((const double *) d2); + return cx_vcmp_double(a, b); +} + +int cx_vcmp_intptr(intptr_t p1, intptr_t p2) { + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} + int cx_cmp_intptr( - void const *ptr1, - void const *ptr2 + const void *ptr1, + const void *ptr2 ) { intptr_t p1 = *(const intptr_t *) ptr1; intptr_t p2 = *(const intptr_t *) ptr2; + return cx_vcmp_intptr(p1, p2); +} + +int cx_vcmp_uintptr(uintptr_t p1, uintptr_t p2) { if (p1 == p2) { return 0; } else { @@ -187,15 +255,23 @@ } int cx_cmp_uintptr( - void const *ptr1, - void const *ptr2 + const void *ptr1, + const void *ptr2 ) { uintptr_t p1 = *(const uintptr_t *) ptr1; uintptr_t p2 = *(const uintptr_t *) ptr2; + return cx_vcmp_uintptr(p1, p2); +} + +int cx_cmp_ptr( + const void *ptr1, + const void *ptr2 +) { + uintptr_t p1 = (uintptr_t) ptr1; + uintptr_t p2 = (uintptr_t) ptr2; if (p1 == p2) { return 0; } else { return p1 < p2 ? -1 : 1; } } -
--- a/src/ucx/cx/allocator.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/allocator.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file allocator.h + * @file allocator.h * Interface for custom allocators. */ @@ -58,16 +58,15 @@ void *data, void *mem, size_t n - ) - __attribute__((__warn_unused_result__)); + ); /** * The allocator's calloc() implementation. */ void *(*calloc)( void *data, - size_t nelem, - size_t n + size_t nmemb, + size_t size ); /** @@ -76,8 +75,7 @@ void (*free)( void *data, void *mem - ) - __attribute__((__nonnull__)); + ); } cx_allocator_class; /** @@ -102,27 +100,28 @@ /** * A default allocator using standard library malloc() etc. */ -extern CxAllocator *cxDefaultAllocator; +cx_attr_export +extern const CxAllocator * const cxDefaultAllocator; /** * Function pointer type for destructor functions. * * A destructor function deallocates possible contents and MAY free the memory - * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param memory a pointer to the object to destruct */ -typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__)); +typedef void (*cx_destructor_func)(void *memory); /** * Function pointer type for destructor functions. * * A destructor function deallocates possible contents and MAY free the memory - * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param data an optional pointer to custom data * @param memory a pointer to the object to destruct @@ -130,108 +129,290 @@ typedef void (*cx_destructor_func2)( void *data, void *memory -) __attribute__((__nonnull__(2))); +); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. * - * \par Error handling - * \c errno will be set by realloc() on failure. + * @par Error handling + * @c errno will be set by realloc() on failure. * * @param mem pointer to the pointer to allocated block * @param n the new size in bytes - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() */ -int cx_reallocate( +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_reallocate_( void **mem, size_t n -) -__attribute__((__nonnull__)); +); + +/** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero failure + * @see cx_reallocate() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_reallocatearray_( + void **mem, + size_t nmemb, + size_t size +); /** - * Allocate \p n bytes of memory. + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * @par Error handling + * @c errno will be set by realloc() on failure. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() + */ +#define cx_reallocate(mem, n) cx_reallocate_((void**)(mem), n) + +/** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cx_reallocatearray(mem, nmemb, size) \ + cx_reallocatearray_((void**)(mem), nmemb, size) + +/** + * Free a block allocated by this allocator. + * + * @note Freeing a block of a different allocator is undefined. + * + * @param allocator the allocator + * @param mem a pointer to the block to free + */ +cx_attr_nonnull_arg(1) +cx_attr_export +void cxFree( + const CxAllocator *allocator, + void *mem +); + +/** + * Allocate @p n bytes of memory. * * @param allocator the allocator * @param n the number of bytes * @return a pointer to the allocated memory */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) +cx_attr_export void *cxMalloc( - CxAllocator const *allocator, + const CxAllocator *allocator, size_t n -) -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2))); +); /** - * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long. - * This function may return the same pointer that was passed to it, if moving the memory - * was not necessary. + * Reallocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. * - * \note Re-allocating a block allocated by a different allocator is undefined. + * @note Re-allocating a block allocated by a different allocator is undefined. * * @param allocator the allocator * @param mem pointer to the previously allocated block * @param n the new size in bytes - * @return a pointer to the re-allocated memory + * @return a pointer to the reallocated memory */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3) +cx_attr_export void *cxRealloc( - CxAllocator const *allocator, + const CxAllocator *allocator, void *mem, size_t n -) -__attribute__((__warn_unused_result__)) -__attribute__((__alloc_size__(3))); +); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. - * This function acts like cxRealloc() using the pointer pointed to by \p mem. + * Reallocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * If that multiplication overflows, this function returns @c NULL and @c errno + * will be set. + * + * @note Re-allocating a block allocated by a different allocator is undefined. * - * \note Re-allocating a block allocated by a different allocator is undefined. + * @param allocator the allocator + * @param mem pointer to the previously allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @return a pointer to the reallocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3, 4) +cx_attr_export +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +); + +/** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. * - * \par Error handling - * \c errno will be set, if the underlying realloc function does so. + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. * * @param allocator the allocator * @param mem pointer to the pointer to allocated block * @param n the new size in bytes - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -int cxReallocate( - CxAllocator const *allocator, +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export +int cxReallocate_( + const CxAllocator *allocator, void **mem, size_t n -) -__attribute__((__nonnull__)); +); + +/** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocate(allocator, mem, n) \ + cxReallocate_(allocator, (void**)(mem), n) /** - * Allocate \p nelem elements of \p n bytes each, all initialized to zero. + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. * * @param allocator the allocator - * @param nelem the number of elements - * @param n the size of each element in bytes + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero on failure + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export +int cxReallocateArray_( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +); + +/** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocateArray(allocator, mem, nmemb, size) \ + cxReallocateArray_(allocator, (void**) (mem), nmemb, size) + +/** + * Allocate @p nmemb elements of @p n bytes each, all initialized to zero. + * + * @param allocator the allocator + * @param nmemb the number of elements + * @param size the size of each element in bytes * @return a pointer to the allocated memory */ +cx_attr_nonnull_arg(1) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2, 3) +cx_attr_export void *cxCalloc( - CxAllocator const *allocator, - size_t nelem, - size_t n -) -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2, 3))); - -/** - * Free a block allocated by this allocator. - * - * \note Freeing a block of a different allocator is undefined. - * - * @param allocator the allocator - * @param mem a pointer to the block to free - */ -void cxFree( - CxAllocator const *allocator, - void *mem -) -__attribute__((__nonnull__)); + const CxAllocator *allocator, + size_t nmemb, + size_t size +); #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/array_list.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/array_list.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,13 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file array_list.h - * \brief Array list implementation. - * \details Also provides several low-level functions for custom array list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file array_list.h + * @brief Array list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ @@ -46,23 +44,148 @@ #endif /** + * The maximum item size in an array list that fits into stack buffer + * when swapped. + */ +cx_attr_export +extern const unsigned cx_array_swap_sbo_size; + +/** + * Declares variables for an array that can be used with the convenience macros. + * + * @par Examples + * @code + * // integer array with at most 255 elements + * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t) + * + * // array of MyObject* pointers where size and capacity are stored as unsigned int + * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int) + * + * // initializing code + * cx_array_initialize(myarray, 16); // reserve space for 16 + * cx_array_initialize(objects, 100); // reserve space for 100 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t) + * + * @see cx_array_initialize() + * @see cx_array_simple_add() + * @see cx_array_simple_copy() + * @see cx_array_simple_add_sorted() + * @see cx_array_simple_insert_sorted() + */ +#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \ + type * name; \ + /** Array size. */ size_type name##_size; \ + /** Array capacity. */ size_type name##_capacity + +/** + * Declares variables for an array that can be used with the convenience macros. + * + * The size and capacity variables will have @c size_t type. + * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. + * + * @par Examples + * @code + * // int array + * CX_ARRAY_DECLARE(int, myarray) + * + * // initializing code + * cx_array_initialize(myarray, 32); // reserve space for 32 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * + * @see cx_array_initialize() + * @see cx_array_simple_add() + * @see cx_array_simple_copy() + * @see cx_array_simple_add_sorted() + * @see cx_array_simple_insert_sorted() + */ +#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t) + +/** + * Initializes an array with the given capacity. + * + * The type of the capacity depends on the type used during declaration. + * + * @par Examples + * @code + * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t) + * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t + * + * // initializing code + * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255 + * cx_array_initialize(arr2, 500); // OK + * @endcode + * + * + * The memory for the array is allocated with stdlib malloc(). + * @param array the name of the array + * @param capacity the initial capacity + * @see cx_array_initialize_a() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() + */ +#define cx_array_initialize(array, capacity) \ + array##_capacity = capacity; \ + array##_size = 0; \ + array = malloc(sizeof(array[0]) * capacity) + +/** + * Initializes an array with the given capacity using the specified allocator. + * + * @par Example + * @code + * CX_ARRAY_DECLARE(int, myarray) + * + * + * const CxAllocator *al = // ... + * cx_array_initialize_a(al, myarray, 128); + * // ... + * cxFree(al, myarray); // don't forget to free with same allocator + * @endcode + * + * The memory for the array is allocated with stdlib malloc(). + * @param allocator (@c CxAllocator*) the allocator + * @param array the name of the array + * @param capacity the initial capacity + * @see cx_array_initialize() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() + */ +#define cx_array_initialize_a(allocator, array, capacity) \ + array##_capacity = capacity; \ + array##_size = 0; \ + array = cxMalloc(allocator, sizeof(array[0]) * capacity) + +/** * Defines a reallocation mechanism for arrays. + * You can create your own, use cx_array_reallocator(), or + * use the #cx_array_default_reallocator. */ struct cx_array_reallocator_s { /** - * Re-allocates space for the given array. + * Reallocates space for the given array. * * Implementations are not required to free the original array. - * This allows re-allocation of static memory by allocating heap memory - * and copying the array contents. The information in \p data can keep - * track of the state of the memory or other additional allocator info. + * This allows reallocation of static memory by allocating heap memory + * and copying the array contents. The information in the custom fields of + * the referenced allocator can be used to track the state of the memory + * or to transport other additional data. * * @param array the array to reallocate * @param capacity the new capacity (number of elements) * @param elem_size the size of each element * @param alloc a reference to this allocator - * @return a pointer to the reallocated memory or \c NULL on failure + * @return a pointer to the reallocated memory or @c NULL on failure */ + cx_attr_nodiscard + cx_attr_nonnull_arg(4) + cx_attr_allocsize(2, 3) void *(*realloc)( void *array, size_t capacity, @@ -89,50 +212,467 @@ }; /** - * Return codes for cx_array_copy(). + * Typedef for the array reallocator struct. + */ +typedef struct cx_array_reallocator_s CxArrayReallocator; + +/** + * A default stdlib-based array reallocator. + */ +cx_attr_export +extern CxArrayReallocator *cx_array_default_reallocator; + +/** + * Creates a new array reallocator. + * + * When @p allocator is @c NULL, the stdlib default allocator will be used. + * + * When @p stackmem is not @c NULL, the reallocator is supposed to be used + * @em only for the specific array that is initially located at @p stackmem. + * When reallocation is needed, the reallocator checks, if the array is + * still located at @p stackmem and copies the contents to the heap. + * + * @note Invoking this function with both arguments @c NULL will return a + * reallocator that behaves like #cx_array_default_reallocator. + * + * @param allocator the allocator this reallocator shall be based on + * @param stackmem the address of the array when the array is initially located + * on the stack or shall not reallocated in place + * @return an array reallocator */ -enum cx_array_copy_result { - CX_ARRAY_COPY_SUCCESS, - CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED, - CX_ARRAY_COPY_REALLOC_FAILED, -}; +cx_attr_export +CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +); + +/** + * Reserves memory for additional elements. + * + * This function checks if the @p capacity of the array is sufficient to hold + * at least @p size plus @p elem_count elements. If not, a reallocation is + * performed with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * This function can be useful to replace subsequent calls to cx_array_copy() + * with one single cx_array_reserve() and then - after guaranteeing a + * sufficient capacity - use simple memmove() or memcpy(). + * + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. + * + * @param array a pointer to the target array + * @param size a pointer to the size of the array + * @param capacity a pointer to the capacity of the array + * @param width the width in bytes for the @p size and @p capacity or zero for default + * @param elem_size the size of one element + * @param elem_count the number of expected additional elements + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() + */ +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_export +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); /** * Copies elements from one array to another. * - * The elements are copied to the \p target array at the specified \p index, - * overwriting possible elements. The \p index does not need to be in range of - * the current array \p size. If the new index plus the number of elements added - * would extend the array's size, and \p capacity is not \c NULL, the remaining - * capacity is used. + * The elements are copied to the @p target array at the specified @p index, + * overwriting possible elements. The @p index does not need to be in range of + * the current array @p size. If the new index plus the number of elements added + * would extend the array's size, the remaining @p capacity is used. + * + * If the @p capacity is also insufficient to hold the new data, a reallocation + * attempt is made with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. * - * If the capacity is insufficient to hold the new data, a reallocation - * attempt is made, unless the allocator is set to \c NULL, in which case - * this function ultimately returns a failure. + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. * - * @param target the target array + * @param target a pointer to the target array * @param size a pointer to the size of the target array - * @param capacity a pointer to the target array's capacity - - * \c NULL if only the size shall be used to bound the array + * @param capacity a pointer to the capacity of the target array + * @param width the width in bytes for the @p size and @p capacity or zero for default * @param index the index where the copied elements shall be placed * @param src the source array * @param elem_size the size of one element * @param elem_count the number of elements to copy - * @param reallocator the array re-allocator to use, or \c NULL - * if re-allocation shall not happen - * @return zero on success, non-zero error code on failure + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() + */ +cx_attr_nonnull_arg(1, 2, 3, 6) +cx_attr_export +int cx_array_copy( + void **target, + void *size, + void *capacity, + unsigned width, + size_t index, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); + +/** + * Convenience macro that uses cx_array_copy() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy() + */ +#define cx_array_simple_copy_a(reallocator, array, index, src, count) \ + cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), index, src, sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_copy() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy_a() + */ +#define cx_array_simple_copy(array, index, src, count) \ + cx_array_simple_copy_a(NULL, array, index, src, count) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected @em additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve() + */ +#define cx_array_simple_reserve_a(reallocator, array, count) \ + cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve_a() */ -enum cx_array_copy_result cx_array_copy( +#define cx_array_simple_reserve(array, count) \ + cx_array_simple_reserve_a(NULL, array, count) + +/** + * Adds an element to an array with the possibility of allocating more space. + * + * The element @p elem is added to the end of the @p target array which contains + * @p size elements, already. The @p capacity must point to a variable denoting + * the current maximum number of elements the array can hold. + * + * If the capacity is insufficient to hold the new element, an attempt to + * increase the @p capacity is made and the new capacity is written back. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure + */ +#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \ + cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \ + *(size), elem, elem_size, 1, reallocator) + +/** + * Convenience macro that uses cx_array_add() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add() + */ +#define cx_array_simple_add_a(reallocator, array, elem) \ + cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1) + +/** + * Convenience macro that uses cx_array_add() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_a() + */ +#define cx_array_simple_add(array, elem) \ + cx_array_simple_add_a(cx_array_default_reallocator, array, elem) + +/** + * Inserts a sorted array into another sorted array. + * + * If either the target or the source array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param cmp_func the compare function for the elements + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to insert + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull_arg(1, 2, 3, 5) +cx_attr_export +int cx_array_insert_sorted( void **target, size_t *size, size_t *capacity, - size_t index, - void const *src, + cx_compare_func cmp_func, + const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator -) __attribute__((__nonnull__(1, 2, 5))); + CxArrayReallocator *reallocator +); + +/** + * Inserts an element into a sorted array. + * + * If the target array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure + */ +#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ + cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) + +/** + * Convenience macro for cx_array_add_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted() + */ +#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \ + cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_sorted() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted_a() + */ +#define cx_array_simple_add_sorted(array, elem, cmp_func) \ + cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted() + */ +#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) +/** + * Convenience macro for cx_array_insert_sorted() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted_a() + */ +#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ + cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) + +/** + * Searches the largest lower bound in a sorted array. + * + * In other words, this function returns the index of the largest element + * in @p arr that is less or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the largest lower bound, or @p size + * @see cx_array_binary_search_sup() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +cx_attr_export +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); + +/** + * Searches an item in a sorted array. + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the element in the array, or @p size if the element + * cannot be found + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search_sup() + */ +cx_attr_nonnull +cx_attr_export +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); + +/** + * Searches the smallest upper bound in a sorted array. + * + * In other words, this function returns the index of the smallest element + * in @p arr that is greater or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the smallest upper bound, or @p size + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +cx_attr_export +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); /** * Swaps two array elements. @@ -142,50 +682,59 @@ * @param idx1 index of first element * @param idx2 index of second element */ +cx_attr_nonnull +cx_attr_export void cx_array_swap( void *arr, size_t elem_size, size_t idx1, size_t idx2 -) __attribute__((__nonnull__)); +); /** - * Allocates an array list for storing elements with \p item_size bytes each. + * Allocates an array list for storing elements with @p elem_size bytes each. * - * If \p item_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements and the compare function will be automatically set + * to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list memory - * (if \c NULL the cxDefaultAllocator will be used) + * (if @c NULL, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL sort and find functions will not work) - * @param item_size the size of each element in bytes + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes * @param initial_capacity the initial number of elements the array can store * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) +cx_attr_export CxList *cxArrayListCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, cx_compare_func comparator, - size_t item_size, + size_t elem_size, size_t initial_capacity ); /** - * Allocates an array list for storing elements with \p item_size bytes each. + * Allocates an array list for storing elements with @p elem_size bytes each. * - * The list will use the cxDefaultAllocator and \em NO compare function. + * The list will use the cxDefaultAllocator and @em NO compare function. * If you want to call functions that need a compare function, you have to * set it immediately after creation or use cxArrayListCreate(). * - * If \p item_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements and the compare function will be automatically set + * to cx_cmp_ptr(), if none is given. * - * @param item_size the size of each element in bytes - * @param initial_capacity the initial number of elements the array can store + * @param elem_size (@c size_t) the size of each element in bytes + * @param initial_capacity (@c size_t) the initial number of elements the array can store * @return the created list */ -#define cxArrayListCreateSimple(item_size, initial_capacity) \ - cxArrayListCreate(NULL, NULL, item_size, initial_capacity) +#define cxArrayListCreateSimple(elem_size, initial_capacity) \ + cxArrayListCreate(NULL, NULL, elem_size, initial_capacity) #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/buffer.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/buffer.h Sun Mar 02 18:10:52 2025 +0100 @@ -27,9 +27,9 @@ */ /** - * \file buffer.h + * @file buffer.h * - * \brief Advanced buffer implementation. + * @brief Advanced buffer implementation. * * Instances of CxBuffer can be used to read from or to write to like one * would do with a stream. @@ -38,10 +38,9 @@ * can be enabled. See the documentation of the macro constants for more * information. * - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_BUFFER_H @@ -50,7 +49,7 @@ #include "common.h" #include "allocator.h" -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -61,16 +60,101 @@ /** * If this flag is enabled, the buffer will automatically free its contents when destroyed. + * + * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically + * set when the copy-on-write operations is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 /** - * If this flag is enabled, the buffer will automatically extends its capacity. + * If this flag is enabled, the buffer will automatically extend its capacity. */ #define CX_BUFFER_AUTO_EXTEND 0x02 +/** + * If this flag is enabled, the buffer will allocate new memory when written to. + * + * The current contents of the buffer will be copied to the new memory and the flag + * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. + */ +#define CX_BUFFER_COPY_ON_WRITE 0x04 + +/** + * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation. + * + * After performing the copy, the flag is automatically cleared. + * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why + * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled. + */ +#define CX_BUFFER_COPY_ON_EXTEND 0x08 + +/** + * Function pointer for cxBufferWrite that is compatible with cx_write_func. + * @see cx_write_func + */ +#define cxBufferWriteFunc ((cx_write_func) cxBufferWrite) +/** + * Function pointer for cxBufferRead that is compatible with cx_read_func. + * @see cx_read_func + */ +#define cxBufferReadFunc ((cx_read_func) cxBufferRead) + +/** + * Configuration for automatic flushing. + */ +struct cx_buffer_flush_config_s { + /** + * The buffer may not extend beyond this threshold before starting to flush. + * + * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. + * The threshold will be the maximum capacity the buffer is extended to + * before flushing. + */ + size_t threshold; + /** + * The block size for the elements to flush. + */ + size_t blksize; + /** + * The maximum number of blocks to flush in one cycle. + * + * @attention while it is guaranteed that cxBufferFlush() will not flush + * more blocks, this is not necessarily the case for cxBufferWrite(). + * After performing a flush cycle, cxBufferWrite() will retry the write + * operation and potentially trigger another flush cycle, until the + * flush target accepts no more data. + */ + size_t blkmax; + + /** + * The target for write function. + */ + void *target; + + /** + * The write-function used for flushing. + * If NULL, the flushed content gets discarded. + */ + cx_write_func wfunc; +}; + +/** + * Type alias for the flush configuration struct. + * + * @code + * struct cx_buffer_flush_config_s { + * size_t threshold; + * size_t blksize; + * size_t blkmax; + * void *target; + * cx_write_func wfunc; + * }; + * @endcode + */ +typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; + /** Structure for the UCX buffer data. */ -typedef struct { +struct cx_buffer_s { /** A pointer to the buffer contents. */ union { /** @@ -83,7 +167,13 @@ unsigned char *bytes; }; /** The allocator to use for automatic memory management. */ - CxAllocator const *allocator; + const CxAllocator *allocator; + /** + * Optional flush configuration + * + * @see cxBufferEnableFlushing() + */ + CxBufferFlushConfig *flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ @@ -91,98 +181,80 @@ /** Current size of the buffer content. */ size_t size; /** - * The buffer may not extend beyond this threshold before starting to flush. - * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled). - */ - size_t flush_threshold; - /** - * The block size for the elements to flush. - * Default is 4096 bytes. - */ - size_t flush_blksize; - /** - * The maximum number of blocks to flush in one cycle. - * Zero disables flushing entirely (this is the default). - * Set this to \c SIZE_MAX to flush the entire buffer. - * - * @attention if the maximum number of blocks multiplied with the block size - * is smaller than the expected contents written to this buffer within one write - * operation, multiple flush cycles are performed after that write. - * That means the total number of blocks flushed after one write to this buffer may - * be larger than \c flush_blkmax. - */ - size_t flush_blkmax; - - /** - * The write function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func flush_func; - - /** - * The target for \c flush_func. - */ - void *flush_target; - - /** * Flag register for buffer features. * @see #CX_BUFFER_DEFAULT * @see #CX_BUFFER_FREE_CONTENTS * @see #CX_BUFFER_AUTO_EXTEND + * @see #CX_BUFFER_COPY_ON_WRITE */ int flags; -} cx_buffer_s; +}; /** * UCX buffer. */ -typedef cx_buffer_s CxBuffer; +typedef struct cx_buffer_s CxBuffer; /** * Initializes a fresh buffer. * - * \note You may provide \c NULL as argument for \p space. + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * + * You need to set the size manually after initialization, if + * you provide @p space which already contains data. + * + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. + * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying + * copy-on-write should be avoided, because the allocated + * space will be leaking after the copy-on-write operation. * * @param buffer the buffer to initialize - * @param space pointer to the memory area, or \c NULL to allocate + * @param space pointer to the memory area, or @c NULL to allocate * new memory * @param capacity the capacity of the buffer * @param allocator the allocator this buffer shall use for automatic - * memory management. If \c NULL, the default heap allocator will be used. + * memory management + * (if @c NULL, a default stdlib allocator will be used) * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) +cx_attr_export int cxBufferInit( CxBuffer *buffer, void *space, size_t capacity, - CxAllocator const *allocator, + const CxAllocator *allocator, int flags ); /** - * Allocates and initializes a fresh buffer. + * Configures the buffer for flushing. * - * \note You may provide \c NULL as argument for \p space. - * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. + * Flushing can happen automatically when data is written + * to the buffer (see cxBufferWrite()) or manually when + * cxBufferFlush() is called. * - * @param space pointer to the memory area, or \c NULL to allocate - * new memory - * @param capacity the capacity of the buffer - * @param allocator the allocator to use for allocating the structure and the automatic - * memory management within the buffer. If \c NULL, the default heap allocator will be used. - * @param flags buffer features (see cx_buffer_s.flags) - * @return a pointer to the buffer on success, \c NULL if a required allocation failed + * @param buffer the buffer + * @param config the flush configuration + * @retval zero success + * @retval non-zero failure + * @see cxBufferFlush() + * @see cxBufferWrite() */ -CxBuffer *cxBufferCreate( - void *space, - size_t capacity, - CxAllocator const *allocator, - int flags +cx_attr_nonnull +cx_attr_export +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config ); /** @@ -194,22 +266,61 @@ * @param buffer the buffer which contents shall be destroyed * @see cxBufferInit() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export void cxBufferDestroy(CxBuffer *buffer); /** * Deallocates the buffer. * * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys - * the contents. If you \em only want to destroy the contents, use cxBufferDestroy(). + * the contents. If you @em only want to destroy the contents, use cxBufferDestroy(). + * + * @remark As with all free() functions, this accepts @c NULL arguments in which + * case it does nothing. * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ -__attribute__((__nonnull__)) +cx_attr_export void cxBufferFree(CxBuffer *buffer); /** + * Allocates and initializes a fresh buffer. + * + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. + * Then this function will allocate the space and enforce + * the #CX_BUFFER_FREE_CONTENTS flag. + * + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param allocator the allocator to use for allocating the structure and the automatic + * memory management within the buffer + * (if @c NULL, a default stdlib allocator will be used) + * @param flags buffer features (see cx_buffer_s.flags) + * @return a pointer to the buffer on success, @c NULL if a required allocation failed + */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard +cx_attr_export +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +); + +/** * Shifts the contents of the buffer by the given offset. * * If the offset is positive, the contents are shifted to the right. @@ -220,7 +331,7 @@ * are discarded. * * If the offset is negative, the contents are shifted to the left where the - * first \p shift bytes are discarded. + * first @p shift bytes are discarded. * The new size of the buffer is the old size minus the absolute shift value. * If this value is larger than the buffer size, the buffer is emptied (but * not cleared, see the security note below). @@ -228,11 +339,11 @@ * The buffer position gets shifted alongside with the content but is kept * within the boundaries of the buffer. * - * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and - * cxBufferShiftRight() functions using a \c size_t as parameter type. + * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and + * cxBufferShiftRight() functions using a @c size_t as parameter type. * - * \attention - * Security Note: The shifting operation does \em not erase the previously occupied memory cells. + * @attention + * Security Note: The shifting operation does @em not erase the previously occupied memory cells. * But you can easily do that manually, e.g. by calling * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code> @@ -240,9 +351,13 @@ * * @param buffer the buffer * @param shift the shift offset (negative means left shift) - * @return 0 on success, non-zero if a required auto-extension fails + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails + * @see cxBufferShiftLeft() + * @see cxBufferShiftRight() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferShift( CxBuffer *buffer, off_t shift @@ -254,10 +369,12 @@ * * @param buffer the buffer * @param shift the shift offset - * @return 0 on success, non-zero if a required auto-extension fails + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -267,15 +384,14 @@ * Shifts the buffer to the left. * See cxBufferShift() for details. * - * \note Since a left shift cannot fail due to memory allocation problems, this - * function always returns zero. - * * @param buffer the buffer * @param shift the positive shift offset - * @return always zero + * @retval zero success + * @retval non-zero if the buffer uses copy-on-write and the allocation fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -285,23 +401,25 @@ /** * Moves the position of the buffer. * - * The new position is relative to the \p whence argument. + * The new position is relative to the @p whence argument. * - * \li \c SEEK_SET marks the start of the buffer. - * \li \c SEEK_CUR marks the current position. - * \li \c SEEK_END marks the end of the buffer. + * @li @c SEEK_SET marks the start of the buffer. + * @li @c SEEK_CUR marks the current position. + * @li @c SEEK_END marks the end of the buffer. * * With an offset of zero, this function sets the buffer position to zero - * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position - * unchanged (\c SEEK_CUR). + * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position + * unchanged (@c SEEK_CUR). * * @param buffer the buffer - * @param offset position offset relative to \p whence - * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END - * @return 0 on success, non-zero if the position is invalid + * @param offset position offset relative to @p whence + * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END + * @retval zero success + * @retval non-zero if the position is invalid * */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -312,21 +430,43 @@ * Clears the buffer by resetting the position and deleting the data. * * The data is deleted by zeroing it with a call to memset(). + * If you do not need that, you can use the faster cxBufferReset(). + * + * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function + * will not erase the data and behave exactly as cxBufferReset(). * * @param buffer the buffer to be cleared + * @see cxBufferReset() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export void cxBufferClear(CxBuffer *buffer); /** - * Tests, if the buffer position has exceeded the buffer capacity. + * Resets the buffer by resetting the position and size to zero. + * + * The data in the buffer is not deleted. If you need a safe + * reset of the buffer, use cxBufferClear(). + * + * @param buffer the buffer to be cleared + * @see cxBufferClear() + */ +cx_attr_nonnull +cx_attr_export +void cxBufferReset(CxBuffer *buffer); + +/** + * Tests, if the buffer position has exceeded the buffer size. * * @param buffer the buffer to test - * @return non-zero, if the current buffer position has exceeded the last - * available byte of the buffer. + * @retval true if the current buffer position has exceeded the last + * byte of the buffer's contents + * @retval false otherwise */ -__attribute__((__nonnull__)) -int cxBufferEof(CxBuffer const *buffer); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +bool cxBufferEof(const CxBuffer *buffer); /** @@ -336,9 +476,11 @@ * * @param buffer the buffer * @param capacity the minimum required capacity for this buffer - * @return 0 on success or a non-zero value on failure + * @retval zero the capacity was already sufficient or successfully increased + * @retval non-zero on allocation failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -347,51 +489,155 @@ /** * Writes data to a CxBuffer. * + * If automatic flushing is not enabled, the data is simply written into the + * buffer at the current position and the position of the buffer is increased + * by the number of bytes written. + * * If flushing is enabled and the buffer needs to flush, the data is flushed to * the target until the target signals that it cannot take more data by * returning zero via the respective write function. In that case, the remaining * data in this buffer is shifted to the beginning of this buffer so that the - * newly available space can be used to append as much data as possible. This - * function only stops writing more elements, when the flush target and this + * newly available space can be used to append as much data as possible. + * + * This function only stops writing more elements, when the flush target and this * buffer are both incapable of taking more data or all data has been written. - * The number returned by this function is the total number of elements that - * could be written during the process. It does not necessarily mean that those - * elements are still in this buffer, because some of them could have also be - * flushed already. + * + * If, after flushing, the number of items that shall be written still exceeds + * the capacity or flush threshold, this function tries to write all items directly + * to the flush target, if possible. + * + * The number returned by this function is the number of elements from + * @c ptr that could be written to either the flush target or the buffer + * (so it does not include the number of items that had been already in the buffer + * in were flushed during the process). + * + * @attention + * When @p size is larger than one and the contents of the buffer are not aligned + * with @p size, flushing stops after all complete items have been flushed, leaving + * the mis-aligned part in the buffer. + * Afterward, this function only writes as many items as possible to the buffer. + * + * @note The signature is compatible with the fwrite() family of functions. * - * If automatic flushing is not enabled, the position of the buffer is increased - * by the number of bytes written. + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to write to + * @return the total count of elements written + * @see cxBufferAppend() + * @see cxBufferRead() + */ +cx_attr_nonnull +cx_attr_export +size_t cxBufferWrite( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Appends data to a CxBuffer. * - * \note The signature is compatible with the fwrite() family of functions. + * The data is always appended to current data within the buffer, + * regardless of the current position. + * This is especially useful when the buffer is primarily meant for reading + * while additional data is added to the buffer occasionally. + * Consequently, the position of the buffer is unchanged after this operation. + * + * @note The signature is compatible with the fwrite() family of functions. * * @param ptr a pointer to the memory area containing the bytes to be written * @param size the length of one element * @param nitems the element count * @param buffer the CxBuffer to write to * @return the total count of elements written + * @see cxBufferWrite() + * @see cxBufferRead() */ -__attribute__((__nonnull__)) -size_t cxBufferWrite( - void const *ptr, +cx_attr_nonnull +cx_attr_export +size_t cxBufferAppend( + const void *ptr, size_t size, size_t nitems, CxBuffer *buffer ); /** + * Performs a single flush-run on the specified buffer. + * + * Does nothing when the position in the buffer is zero. + * Otherwise, the data until the current position minus + * one is considered for flushing. + * Note carefully that flushing will never exceed the + * current @em position, even when the size of the + * buffer is larger than the current position. + * + * One flush run will try to flush @c blkmax many + * blocks of size @c blksize until either the @p buffer + * has no more data to flush or the write function + * used for flushing returns zero. + * + * The buffer is shifted left for that many bytes + * the flush operation has successfully flushed. + * + * @par Example 1 + * Assume you have a buffer with size 340 and you are + * at position 200. The flush configuration is + * @c blkmax=4 and @c blksize=64 . + * Assume that the entire flush operation is successful. + * All 200 bytes on the left hand-side from the current + * position are written. + * That means, the size of the buffer is now 140 and the + * position is zero. + * + * @par Example 2 + * Same as Example 1, but now the @c blkmax is 1. + * The size of the buffer is now 276 and the position is 136. + * + * @par Example 3 + * Same as Example 1, but now assume the flush target + * only accepts 100 bytes before returning zero. + * That means, the flush operations manages to flush + * one complete block and one partial block, ending + * up with a buffer with size 240 and position 100. + * + * @remark Just returns zero when flushing was not enabled with + * cxBufferEnableFlushing(). + * + * @remark When the buffer uses copy-on-write, the memory + * is copied first, before attempting any flush. + * This is, however, considered an erroneous use of the + * buffer, because it does not make much sense to put + * readonly data into an UCX buffer for flushing, instead + * of writing it directly to the target. + * + * @param buffer the buffer + * @return the number of successfully flushed bytes + * @see cxBufferEnableFlushing() + */ +cx_attr_nonnull +cx_attr_export +size_t cxBufferFlush(CxBuffer *buffer); + +/** * Reads data from a CxBuffer. * * The position of the buffer is increased by the number of bytes read. * - * \note The signature is compatible with the fread() family of functions. + * @note The signature is compatible with the fread() family of functions. * * @param ptr a pointer to the memory area where to store the read data * @param size the length of one element * @param nitems the element count * @param buffer the CxBuffer to read from * @return the total number of elements read + * @see cxBufferWrite() + * @see cxBufferAppend() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export size_t cxBufferRead( void *ptr, size_t size, @@ -404,30 +650,55 @@ * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is - * disabled or buffer extension fails, \c EOF is returned. + * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature + * is disabled or buffer extension fails, @c EOF is returned. * * On successful write, the position of the buffer is increased. * + * If you just want to write a null-terminator at the current position, you + * should use cxBufferTerminate() instead. + * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has bean written or \c EOF when the end of the stream is + * @return the byte that has been written or @c EOF when the end of the stream is * reached and automatic extension is not enabled or not possible + * @see cxBufferTerminate() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferPut( CxBuffer *buffer, int c ); /** + * Writes a terminating zero to a buffer at the current position. + * + * On successful write, @em neither the position @em nor the size of the buffer is + * increased. + * + * The purpose of this function is to have the written data ready to be used as + * a C string. + * + * @param buffer the buffer to write to + * @return zero, if the terminator could be written, non-zero otherwise + */ +cx_attr_nonnull +cx_attr_export +int cxBufferTerminate(CxBuffer *buffer); + +/** * Writes a string to a buffer. * + * This is a convenience function for <code>cxBufferWrite(str, 1, strlen(str), buffer)</code>. + * * @param buffer the buffer * @param str the zero-terminated string * @return the number of bytes written */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) +cx_attr_export size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -439,9 +710,10 @@ * The current position of the buffer is increased after a successful read. * * @param buffer the buffer to read from - * @return the character or \c EOF, if the end of the buffer is reached + * @return the character or @c EOF, if the end of the buffer is reached */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus
--- a/src/ucx/cx/collection.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/collection.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file collection.h - * \brief Common definitions for various collection implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file collection.h + * @brief Common definitions for various collection implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_COLLECTION_H @@ -39,6 +38,7 @@ #include "allocator.h" #include "iterator.h" +#include "compare.h" #ifdef __cplusplus extern "C" { @@ -50,56 +50,125 @@ #define CX_STORE_POINTERS 0 /** - * A comparator function comparing two collection elements. + * Base attributes of a collection. */ -typedef int(*cx_compare_func)( - void const *left, - void const *right -); +struct cx_collection_s { + /** + * The allocator to use. + */ + const CxAllocator *allocator; + /** + * The comparator function for the elements. + */ + cx_compare_func cmpfunc; + /** + * The size of each element. + */ + size_t elem_size; + /** + * The number of currently stored elements. + */ + size_t size; + /** + * An optional simple destructor for the collection's elements. + * + * @attention Read the documentation of the particular collection implementation + * whether this destructor shall only destroy the contents or also free the memory. + */ + cx_destructor_func simple_destructor; + /** + * An optional advanced destructor for the collection's elements. + * + * @attention Read the documentation of the particular collection implementation + * whether this destructor shall only destroy the contents or also free the memory. + */ + cx_destructor_func2 advanced_destructor; + /** + * The pointer to additional data that is passed to the advanced destructor. + */ + void *destructor_data; + /** + * Indicates if this list is supposed to store pointers + * instead of copies of the actual objects. + */ + bool store_pointer; + /** + * Indicates if this collection is guaranteed to be sorted. + * Note that the elements can still be sorted, even when the collection is not aware of that. + */ + bool sorted; +}; /** * Use this macro to declare common members for a collection structure. + * + * @par Example Use + * @code + * struct MyCustomSet { + * CX_COLLECTION_BASE; + * MySetElements *data; + * } + * @endcode */ -#define CX_COLLECTION_MEMBERS \ - /** \ - * The allocator to use. \ - */ \ - CxAllocator const *allocator; \ - /** \ - * The comparator function for the elements. \ - */ \ - cx_compare_func cmpfunc; \ - /** \ - * The size of each element. \ - */ \ - size_t item_size; \ - /** \ - * The number of currently stored elements. \ - */ \ - size_t size; \ - /** \ - * An optional simple destructor for the collection's elements. \ - * \ - * @attention Read the documentation of the particular collection implementation \ - * whether this destructor shall only destroy the contents or also free the memory. \ - */ \ - cx_destructor_func simple_destructor; \ - /** \ - * An optional advanced destructor for the collection's elements. \ - * \ - * @attention Read the documentation of the particular collection implementation \ - * whether this destructor shall only destroy the contents or also free the memory. \ - */ \ - cx_destructor_func2 advanced_destructor; \ - /** \ - * The pointer to additional data that is passed to the advanced destructor. \ - */ \ - void *destructor_data; \ - /** \ - * Indicates if this instance of a collection is supposed to store pointers \ - * instead of copies of the actual objects. \ - */ \ - bool store_pointer; +#define CX_COLLECTION_BASE struct cx_collection_s collection + +/** + * Returns the number of elements currently stored. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @return (@c size_t) the number of currently stored elements + */ +#define cxCollectionSize(c) ((c)->collection.size) + +/** + * Returns the size of one element. + * + * If #cxCollectionStoresPointers() returns true, this is the size of a pointer. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @return (@c size_t) the size of one element in bytes + */ +#define cxCollectionElementSize(c) ((c)->collection.elem_size) + +/** + * Indicates whether this collection only stores pointers instead of the actual data. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @retval true if this collection stores only pointers to data + * @retval false if this collection stores the actual element's data + */ +#define cxCollectionStoresPointers(c) ((c)->collection.store_pointer) + +/** + * Indicates whether the collection can guarantee that the stored elements are currently sorted. + * + * This may return false even when the elements are sorted. + * It is totally up to the implementation of the collection whether it keeps track of the order of its elements. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @retval true if the elements are currently sorted wrt. the collection's compare function + * @retval false if the order of elements is unknown + */ +#define cxCollectionSorted(c) ((c)->collection.sorted) + +/** + * Sets a simple destructor function for this collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param destr the destructor function + */ +#define cxDefineDestructor(c, destr) \ + (c)->collection.simple_destructor = (cx_destructor_func) destr + +/** + * Sets a simple destructor function for this collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param destr the destructor function + */ +#define cxDefineAdvancedDestructor(c, destr, data) \ + (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \ + (c)->collection.destructor_data = data /** * Invokes the simple destructor function for a specific element. @@ -107,11 +176,14 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_simple_destructor(c, e) \ - (c)->simple_destructor((c)->store_pointer ? (*((void **) (e))) : (e)) + (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e)) /** * Invokes the advanced destructor function for a specific element. @@ -119,12 +191,15 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_advanced_destructor(c, e) \ - (c)->advanced_destructor((c)->destructor_data, \ - (c)->store_pointer ? (*((void **) (e))) : (e)) + (c)->collection.advanced_destructor((c)->collection.destructor_data, \ + (c)->collection.store_pointer ? (*((void **) (e))) : (e)) /** @@ -133,12 +208,15 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_destructor(c, e) \ - if ((c)->simple_destructor) cx_invoke_simple_destructor(c,e); \ - if ((c)->advanced_destructor) cx_invoke_advanced_destructor(c,e) + if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \ + if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e) #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/common.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/common.h Sun Mar 02 18:10:52 2025 +0100 @@ -27,16 +27,15 @@ */ /** - * \file common.h + * @file common.h * - * \brief Common definitions and feature checks. + * @brief Common definitions and feature checks. * - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License * - * \mainpage UAP Common Extensions + * @mainpage UAP Common Extensions * Library with common and useful functions, macros and data structures. * <p> * Latest available source:<br> @@ -47,7 +46,7 @@ * Repositories:<br> * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a> * - or - - * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a> + * <a href="https://uap-core.de/hg/ucx">https://uap-core.de/hg/ucx</a> * </p> * * <h2>LICENCE</h2> @@ -84,12 +83,14 @@ #define UCX_VERSION_MAJOR 3 /** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 0 +#define UCX_VERSION_MINOR 1 /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) -// Common Includes +// --------------------------------------------------------------------------- +// Common includes +// --------------------------------------------------------------------------- #include <stdlib.h> #include <stddef.h> @@ -97,11 +98,202 @@ #include <stdint.h> #include <sys/types.h> +// --------------------------------------------------------------------------- +// Architecture Detection +// --------------------------------------------------------------------------- + +#ifndef INTPTR_MAX +#error Missing INTPTR_MAX definition +#endif +#if INTPTR_MAX == INT64_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 64 +#elif INTPTR_MAX == INT32_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 32 +#else +#error Unknown pointer size or missing size macros! +#endif + +// --------------------------------------------------------------------------- +// Attribute definitions +// --------------------------------------------------------------------------- + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +/** + * All pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull __attribute__((__nonnull__)) + +/** + * The specified pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__))) + +/** + * The returned value is guaranteed to be non-NULL. + */ +#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__)) + +/** + * The attributed function always returns freshly allocated memory. + */ +#define cx_attr_malloc __attribute__((__malloc__)) + +#ifndef __clang__ +/** + * The pointer returned by the attributed function is supposed to be freed + * by @p freefunc. + * + * @param freefunc the function that shall be used to free the memory + * @param freefunc_arg the index of the pointer argument in @p freefunc + */ +#define cx_attr_dealloc(freefunc, freefunc_arg) \ + __attribute__((__malloc__(freefunc, freefunc_arg))) +#else +/** + * Not supported in clang. + */ +#define cx_attr_dealloc(...) +#endif // __clang__ + +/** + * Shortcut to specify #cxFree() as deallocator. + */ +#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2) + +/** + * Specifies the parameters from which the allocation size is calculated. + */ +#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__))) + + +#ifdef __clang__ +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +/** + * No support for access attribute in clang. + */ +#define cx_attr_access(mode, ...) +#else +#if __GNUC__ < 10 +/** + * No support for access attribute in GCC < 10. + */ +#define cx_attr_access(mode, ...) +#else +/** + * Helper macro to define access macros. + */ +#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__))) +#endif // __GNUC__ < 10 +#if __GNUC__ < 14 +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +#else +/** + * The specified argument is expected to be a zero-terminated string. + * + * @param idx the index of the argument + */ +#define cx_attr_cstr_arg(idx) \ + __attribute__((__null_terminated_string_arg__(idx))) +#endif // __GNUC__ < 14 +#endif // __clang__ + + +/** + * Specifies that the function will only read through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__) + +/** + * Specifies that the function will read and write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__) + +/** + * Specifies that the function will only write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) + +#if __STDC_VERSION__ >= 202300L + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused [[maybe_unused]] + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard [[nodiscard]] + +#else // no C23 + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused __attribute__((__unused__)) + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard __attribute__((__warn_unused_result__)) + +#endif // __STDC_VERSION__ + + +// --------------------------------------------------------------------------- +// MSVC specifics +// --------------------------------------------------------------------------- + +#ifdef _MSC_VER +// fix missing _Thread_local support +#define _Thread_local __declspec(thread) +#endif // _MSC_VER + +#if defined(CX_WINDLL_EXPORT) +#define cx_attr_export __declspec(dllexport) +#elif defined(CX_WINDLL) +#define cx_attr_export __declspec(dllimport) +#else +/** Only used for building Windows DLLs. */ +#define cx_attr_export +#endif // CX_WINDLL / CX_WINDLL_EXPORT + +// --------------------------------------------------------------------------- +// Useful function pointers +// --------------------------------------------------------------------------- + /** * Function pointer compatible with fwrite-like functions. */ typedef size_t (*cx_write_func)( - void const *, + const void *, size_t, size_t, void * @@ -117,25 +309,59 @@ void * ); +// --------------------------------------------------------------------------- +// Utility macros +// --------------------------------------------------------------------------- -// Compiler specific stuff - -#ifndef __GNUC__ /** - * Removes GNU C attributes where they are not supported. + * Determines the number of members in a static C array. + * + * @attention never use this to determine the size of a dynamically allocated + * array. + * + * @param arr the array identifier + * @return the number of elements */ -#define __attribute__(x) -#endif +#define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0])) + +// --------------------------------------------------------------------------- +// szmul implementation +// --------------------------------------------------------------------------- -#ifdef _MSC_VER +#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) +#define CX_SZMUL_BUILTIN +#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) +#else // no GNUC or clang bultin +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a (@c size_t) first operand + * @param b (@c size_t) second operand + * @param result (@c size_t*) a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow + */ +#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) -// fix missing ssize_t definition -#include <BaseTsd.h> -typedef SSIZE_T ssize_t; +/** + * Implementation of cx_szmul() when no compiler builtin is available. + * + * Do not use in application code. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow + */ +#if __cplusplus +extern "C" +#endif +cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result); +#endif // cx_szmul -// fix missing _Thread_local support -#define _Thread_local __declspec(thread) -#endif #endif // UCX_COMMON_H
--- a/src/ucx/cx/compare.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/compare.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file compare.h - * \brief A collection of simple compare functions. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file compare.h + * @brief A collection of simple compare functions. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_COMPARE_H @@ -44,174 +43,518 @@ #endif /** + * A comparator function comparing two arbitrary values. + * + * All functions from compare.h with the cx_cmp prefix are + * compatible with this signature and can be used as + * compare function for collections, or other implementations + * that need to be type-agnostic. + * + * For simple comparisons the cx_vcmp family of functions + * can be used, but they are NOT compatible with this function + * pointer. + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +typedef int (*cx_compare_func)( + const void *left, + const void *right +); + +/** * Compares two integers of type int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to integer one * @param i2 pointer to integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_int(void const *i1, void const *i2); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_int(const void *i1, const void *i2); + +/** + * Compares two integers of type int. + * + * @param i1 integer one + * @param i2 integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_int(int i1, int i2); /** * Compares two integers of type long int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to long integer one * @param i2 pointer to long integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_longint(void const *i1, void const *i2); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_longint(const void *i1, const void *i2); + +/** + * Compares two integers of type long int. + * + * @param i1 long integer one + * @param i2 long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_longint(long int i1, long int i2); /** * Compares two integers of type long long. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to long long one * @param i2 pointer to long long two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_longlong(const void *i1, const void *i2); + +/** + * Compares two integers of type long long. + * + * @param i1 long long int one + * @param i2 long long int two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_longlong(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_longlong(long long int i1, long long int i2); + +/** + * Compares two integers of type int16_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_int16(const void *i1, const void *i2); /** * Compares two integers of type int16_t. * - * @param i1 pointer to int16_t one - * @param i2 pointer to int16_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @param i1 int16_t one + * @param i2 int16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_int16(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_int16(int16_t i1, int16_t i2); /** * Compares two integers of type int32_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to int32_t one * @param i2 pointer to int32_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_int32(const void *i1, const void *i2); + +/** + * Compares two integers of type int32_t. + * + * @param i1 int32_t one + * @param i2 int32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_int32(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_int32(int32_t i1, int32_t i2); + +/** + * Compares two integers of type int64_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_int64(const void *i1, const void *i2); /** * Compares two integers of type int64_t. * - * @param i1 pointer to int64_t one - * @param i2 pointer to int64_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @param i1 int64_t one + * @param i2 int64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_int64(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_int64(int64_t i1, int64_t i2); + +/** + * Compares two integers of type unsigned int. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_uint(const void *i1, const void *i2); /** * Compares two integers of type unsigned int. * - * @param i1 pointer to unsigned integer one - * @param i2 pointer to unsigned integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @param i1 unsigned integer one + * @param i2 unsigned integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uint(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_uint(unsigned int i1, unsigned int i2); /** * Compares two integers of type unsigned long int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to unsigned long integer one * @param i2 pointer to unsigned long integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_ulongint(void const *i1, void const *i2); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_ulongint(const void *i1, const void *i2); + +/** + * Compares two integers of type unsigned long int. + * + * @param i1 unsigned long integer one + * @param i2 unsigned long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); /** * Compares two integers of type unsigned long long. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to unsigned long long one * @param i2 pointer to unsigned long long two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_ulonglong(const void *i1, const void *i2); + +/** + * Compares two integers of type unsigned long long. + * + * @param i1 unsigned long long one + * @param i2 unsigned long long two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_ulonglong(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); + +/** + * Compares two integers of type uint16_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_uint16(const void *i1, const void *i2); /** * Compares two integers of type uint16_t. * - * @param i1 pointer to uint16_t one - * @param i2 pointer to uint16_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @param i1 uint16_t one + * @param i2 uint16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uint16(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_uint16(uint16_t i1, uint16_t i2); + +/** + * Compares two integers of type uint32_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_uint32(const void *i1, const void *i2); /** * Compares two integers of type uint32_t. * - * @param i1 pointer to uint32_t one - * @param i2 pointer to uint32_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @param i1 uint32_t one + * @param i2 uint32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uint32(void const *i1, void const *i2); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_uint32(uint32_t i1, uint32_t i2); /** * Compares two integers of type uint64_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to uint64_t one * @param i2 pointer to uint64_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uint64(void const *i1, void const *i2); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_uint64(const void *i1, const void *i2); + +/** + * Compares two integers of type uint64_t. + * + * @param i1 uint64_t one + * @param i2 uint64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_uint64(uint64_t i1, uint64_t i2); /** * Compares two real numbers of type float with precision 1e-6f. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param f1 pointer to float one * @param f2 pointer to float two - * @return -1, if *f1 is less than *f2, 0 if both are equal, - * 1 if *f1 is greater than *f2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_float(const void *f1, const void *f2); + +/** + * Compares two real numbers of type float with precision 1e-6f. + * + * @param f1 float one + * @param f2 float two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_float(float f1, float f2); -int cx_cmp_float(void const *f1, void const *f2); +/** + * Compares two real numbers of type double with precision 1e-14. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param d1 pointer to double one + * @param d2 pointer to double two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_double(const void *d1, const void *d2); /** * Compares two real numbers of type double with precision 1e-14. * - * @param d1 pointer to double one - * @param d2 pointer to double two - * @return -1, if *d1 is less than *d2, 0 if both are equal, - * 1 if *d1 is greater than *d2 + * @param d1 double one + * @param d2 double two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_double( - void const *d1, - void const *d2 -); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_double(double d1, double d2); + +/** + * Compares the integer representation of two pointers. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param ptr1 pointer to pointer one (const intptr_t*) + * @param ptr2 pointer to pointer two (const intptr_t*) + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_intptr(const void *ptr1, const void *ptr2); /** * Compares the integer representation of two pointers. * - * @param ptr1 pointer to pointer one (intptr_t const*) - * @param ptr2 pointer to pointer two (intptr_t const*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_intptr( - void const *ptr1, - void const *ptr2 -); +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** * Compares the unsigned integer representation of two pointers. * - * @param ptr1 pointer to pointer one (uintptr_t const*) - * @param ptr2 pointer to pointer two (uintptr_t const*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param ptr1 pointer to pointer one (const uintptr_t*) + * @param ptr2 pointer to pointer two (const uintptr_t*) + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uintptr( - void const *ptr1, - void const *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_uintptr(const void *ptr1, const void *ptr2); + +/** + * Compares the unsigned integer representation of two pointers. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); + +/** + * Compares the pointers specified in the arguments without dereferencing. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/hash_key.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/hash_key.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_key.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file hash_key.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ @@ -39,6 +38,7 @@ #define UCX_HASH_KEY_H #include "common.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -47,7 +47,7 @@ /** Internal structure for a key within a hash map. */ struct cx_hash_key_s { /** The key data. */ - void const *data; + const void *data; /** * The key data length. */ @@ -62,15 +62,21 @@ typedef struct cx_hash_key_s CxHashKey; /** - * Computes a murmur2 32 bit hash. + * Computes a murmur2 32-bit hash. * - * You need to initialize \c data and \c len in the key struct. + * You need to initialize @c data and @c len in the key struct. * The hash is then directly written to that struct. * - * \note If \c data is \c NULL, the hash is defined as 1574210520. + * Usually you should not need this function. + * Use cx_hash_key(), instead. + * + * @note If @c data is @c NULL, the hash is defined as 1574210520. * * @param key the key, the hash shall be computed for + * @see cx_hash_key() */ +cx_attr_nonnull +cx_attr_export void cx_hash_murmur(CxHashKey *key); /** @@ -81,8 +87,10 @@ * @param str the string * @return the hash key */ -__attribute__((__warn_unused_result__)) -CxHashKey cx_hash_key_str(char const *str); +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_export +CxHashKey cx_hash_key_str(const char *str); /** * Computes a hash key from a byte array. @@ -91,9 +99,11 @@ * @param len the length * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) +cx_attr_export CxHashKey cx_hash_key_bytes( - unsigned char const *bytes, + const unsigned char *bytes, size_t len ); @@ -108,9 +118,11 @@ * @param len the length of object in memory * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) +cx_attr_export CxHashKey cx_hash_key( - void const *obj, + const void *obj, size_t len ); @@ -120,7 +132,18 @@ * @param str the string * @return the hash key */ -#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length) +cx_attr_nodiscard +static inline CxHashKey cx_hash_key_cxstr(cxstring str) { + return cx_hash_key(str.ptr, str.length); +} + +/** + * Computes a hash key from a UCX string. + * + * @param str (@c cxstring or @c cxmutstr) the string + * @return (@c CxHashKey) the hash key + */ +#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/hash_map.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/hash_map.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_map.h - * \brief Hash map implementation. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file hash_map.h + * @brief Hash map implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_HASH_MAP_H @@ -68,23 +67,27 @@ /** * Creates a new hash map with the specified number of buckets. * - * If \p buckets is zero, an implementation defined default will be used. + * If @p buckets is zero, an implementation defined default will be used. * - * If \p item_size is CX_STORE_POINTERS, the created map will be created as if - * cxMapStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of + * copies of the added elements. * * @note Iterators provided by this hash map implementation provide the remove operation. * The index value of an iterator is incremented when the iterator advanced without removal. - * In other words, when the iterator is finished, \c index==size . + * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use + * (if @c NULL, a default stdlib allocator will be used) * @param itemsize the size of one element * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMapFree, 1) +cx_attr_export CxMap *cxHashMapCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, size_t itemsize, size_t buckets ); @@ -92,23 +95,22 @@ /** * Creates a new hash map with a default number of buckets. * - * If \p item_size is CX_STORE_POINTERS, the created map will be created as if - * cxMapStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of + * copies of the added elements. * * @note Iterators provided by this hash map implementation provide the remove operation. * The index value of an iterator is incremented when the iterator advanced without removal. - * In other words, when the iterator is finished, \c index==size . + * In other words, when the iterator is finished, @c index==size . * - * @param itemsize the size of one element - * @return a pointer to the new hash map + * @param itemsize (@c size_t) the size of one element + * @return (@c CxMap*) a pointer to the new hash map */ -#define cxHashMapCreateSimple(itemsize) \ - cxHashMapCreate(cxDefaultAllocator, itemsize, 0) +#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0) /** * Increases the number of buckets, if necessary. * - * The load threshold is \c 0.75*buckets. If the element count exceeds the load + * The load threshold is @c 0.75*buckets. If the element count exceeds the load * threshold, the map will be rehashed. Otherwise, no action is performed and * this function simply returns 0. * @@ -121,9 +123,11 @@ * @note If the specified map is not a hash map, the behavior is undefined. * * @param map the map to rehash - * @return zero on success, non-zero if a memory allocation error occurred + * @retval zero success + * @retval non-zero if a memory allocation error occurred */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxMapRehash(CxMap *map);
--- a/src/ucx/cx/iterator.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/iterator.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file iterator.h - * \brief Interface for iterator implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file iterator.h + * @brief Interface for iterator implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_ITERATOR_H @@ -39,51 +38,41 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + /** - * The base of mutating and non-mutating iterators. + * Common data for all iterators. */ struct cx_iterator_base_s { /** - * True iff the iterator points to valid data. + * True if the iterator points to valid data. */ - __attribute__ ((__nonnull__)) - bool (*valid)(void const *); + bool (*valid)(const void *); /** * Returns a pointer to the current element. * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) - void *(*current)(void const *); + void *(*current)(const void *); /** * Original implementation in case the function needs to be wrapped. */ - __attribute__ ((__nonnull__)) - void *(*current_impl)(void const *); + void *(*current_impl)(const void *); /** * Advances the iterator. * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) void (*next)(void *); - - /** - * Flag current element for removal, if possible. - * - * When valid returns false, the behavior of this function is undefined. - */ - __attribute__ ((__nonnull__)) - bool (*flag_removal)(void *); - /** * Indicates whether this iterator may remove elements. */ bool mutating; - /** * Internal flag for removing the current element when advancing. */ @@ -91,138 +80,82 @@ }; /** - * Internal iterator struct - use CxMutIterator. + * Convenience type definition for the base structure of an iterator. + * @see #CX_ITERATOR_BASE + */ +typedef struct cx_iterator_base_s CxIteratorBase; + +/** + * Declares base attributes for an iterator. + * Must be the first member of an iterator structure. */ -struct cx_mut_iterator_s { +#define CX_ITERATOR_BASE struct cx_iterator_base_s base + +/** + * Internal iterator struct - use CxIterator. + */ +struct cx_iterator_s { + /** + * Inherited common data for all iterators. + */ + CX_ITERATOR_BASE; /** - * The base properties of this iterator. - */ - struct cx_iterator_base_s base; - - /** - * Handle for the current element, if required. + * Handle for the current element. */ void *elem_handle; /** * Handle for the source collection, if any. */ - void *src_handle; - - /** - * Field for storing a key-value pair. - * May be used by iterators that iterate over k/v-collections. - */ - struct { + union { /** - * A pointer to the key. + * Access for mutating iterators. */ - void const *key; + void *m; /** - * A pointer to the value. + * Access for normal iterators. */ - void *value; - } kv_data; - - /** - * Field for storing a slot number. - * May be used by iterators that iterate over multi-bucket collections. - */ - size_t slot; + const void *c; + } src_handle; /** * If the iterator is position-aware, contains the index of the element in the underlying collection. * Otherwise, this field is usually uninitialized. */ size_t index; + + /** + * The size of an individual element. + */ + size_t elem_size; + + /** + * May contain the total number of elements, if known. + * Shall be set to @c SIZE_MAX when the total number is unknown during iteration. + */ + size_t elem_count; }; /** - * Mutating iterator value type. - * - * An iterator points to a certain element in an (possibly unbounded) chain of elements. - * Iterators that are based on collections (which have a defined "first" element), are supposed - * to be "position-aware", which means that they keep track of the current index within the collection. - * - * @note Objects that are pointed to by an iterator are mutable through that iterator. However, if the - * iterator is based on a collection and the underlying collection is mutated by other means than this iterator - * (e.g. elements added or removed), the iterator becomes invalid (regardless of what cxIteratorValid() returns) - * and MUST be re-obtained from the collection. + * Iterator type. * - * @see CxIterator - */ -typedef struct cx_mut_iterator_s CxMutIterator; - -/** - * Internal iterator struct - use CxIterator. - */ -struct cx_iterator_s { - - /** - * The base properties of this iterator. - */ - struct cx_iterator_base_s base; - - /** - * Handle for the current element, if required. - */ - void *elem_handle; - - /** - * Handle for the source collection, if any. - */ - void const *src_handle; - - /** - * Field for storing a key-value pair. - * May be used by iterators that iterate over k/v-collections. - */ - struct { - /** - * A pointer to the key. - */ - void const *key; - /** - * A pointer to the value. - */ - void *value; - } kv_data; - - /** - * Field for storing a slot number. - * May be used by iterators that iterate over multi-bucket collections. - */ - size_t slot; - - /** - * If the iterator is position-aware, contains the index of the element in the underlying collection. - * Otherwise, this field is usually uninitialized. - */ - size_t index; -}; - -/** - * Iterator value type. * An iterator points to a certain element in a (possibly unbounded) chain of elements. * Iterators that are based on collections (which have a defined "first" element), are supposed * to be "position-aware", which means that they keep track of the current index within the collection. * * @note Objects that are pointed to by an iterator are always mutable through that iterator. However, - * this iterator cannot mutate the collection itself (add or remove elements) and any mutation of the - * collection by other means makes this iterator invalid (regardless of what cxIteratorValid() returns). - * - * @see CxMutIterator + * any concurrent mutation of the collection other than by this iterator makes this iterator invalid, + * and it must not be used anymore. */ typedef struct cx_iterator_s CxIterator; /** * Checks if the iterator points to valid data. * - * This is especially false for past-the-end iterators. - * * @param iter the iterator - * @return true iff the iterator points to valid data + * @retval true if the iterator points to valid data + * @retval false if the iterator already moved past the end */ #define cxIteratorValid(iter) (iter).base.valid(&(iter)) @@ -233,6 +166,7 @@ * * @param iter the iterator * @return a pointer to the current element + * @see cxIteratorValid() */ #define cxIteratorCurrent(iter) (iter).base.current(&iter) @@ -244,15 +178,27 @@ #define cxIteratorNext(iter) (iter).base.next(&iter) /** - * Flags the current element for removal. + * Flags the current element for removal, if this iterator is mutating. + * + * Does nothing for non-mutating iterators. * * @param iter the iterator - * @return false if this iterator cannot remove the element */ -#define cxIteratorFlagRemoval(iter) (iter).base.flag_removal(&iter) +#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating + +/** + * Obtains a reference to an arbitrary iterator. + * + * This is useful for APIs that expect some iterator as an argument. + * + * @param iter the iterator + * @return (@c struct @c cx_iterator_base_s*) a pointer to the iterator + */ +#define cxIteratorRef(iter) &((iter).base) /** * Loops over an iterator. + * * @param type the type of the elements * @param elem the name of the iteration variable * @param iter the iterator @@ -260,4 +206,108 @@ #define cx_foreach(type, elem, iter) \ for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter)) + +/** + * Creates an iterator for the specified plain array. + * + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. + * + * This iterator yields the addresses of the array elements. + * If you want to iterator over an array of pointers, you can + * use cxIteratorPtr() to create an iterator which directly + * yields the stored pointers. + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_size the size of one array element + * @param elem_count the number of elements in the array + * @return an iterator for the specified array + * @see cxIteratorPtr() + */ +cx_attr_nodiscard +cx_attr_export +CxIterator cxIterator( + const void *array, + size_t elem_size, + size_t elem_count +); + +/** + * Creates a mutating iterator for the specified plain array. + * + * While the iterator is in use, the array may only be altered by removing + * elements through #cxIteratorFlagRemoval(). Every other change to the array + * will bring this iterator to an undefined state. + * + * When @p remove_keeps_order is set to @c false, removing an element will only + * move the last element to the position of the removed element, instead of + * moving all subsequent elements by one. Usually, when the order of elements is + * not important, this parameter should be set to @c false. + * + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. + * + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_size the size of one array element + * @param elem_count the number of elements in the array + * @param remove_keeps_order @c true if the order of elements must be preserved + * when removing an element + * @return an iterator for the specified array + */ +cx_attr_nodiscard +cx_attr_export +CxIterator cxMutIterator( + void *array, + size_t elem_size, + size_t elem_count, + bool remove_keeps_order +); + +/** + * Creates an iterator for the specified plain pointer array. + * + * This iterator assumes that every element in the array is a pointer + * and yields exactly those pointers during iteration (while in contrast + * an iterator created with cxIterator() would return the addresses + * of those pointers within the array). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @return an iterator for the specified array + * @see cxIterator() + */ +cx_attr_nodiscard +cx_attr_export +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +); + +/** + * Creates a mutating iterator for the specified plain pointer array. + * + * This is the mutating variant of cxIteratorPtr(). See also + * cxMutIterator(). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @param remove_keeps_order @c true if the order of elements must be preserved + * when removing an element + * @return an iterator for the specified array + * @see cxMutIterator() + * @see cxIteratorPtr() + */ +cx_attr_nodiscard +cx_attr_export +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +); + +#ifdef __cplusplus +} // extern "C" +#endif + #endif // UCX_ITERATOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/cx/json.h Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,1390 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file json.h + * @brief Interface for parsing data from JSON files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_JSON_H +#define UCX_JSON_H + +#include "common.h" +#include "allocator.h" +#include "string.h" +#include "buffer.h" +#include "array_list.h" + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The type of the parsed token. + */ +enum cx_json_token_type { + /** + * No complete token parsed, yet. + */ + CX_JSON_NO_TOKEN, + /** + * The presumed token contains syntactical errors. + */ + CX_JSON_TOKEN_ERROR, + /** + * A "begin of array" '[' token. + */ + CX_JSON_TOKEN_BEGIN_ARRAY, + /** + * A "begin of object" '{' token. + */ + CX_JSON_TOKEN_BEGIN_OBJECT, + /** + * An "end of array" ']' token. + */ + CX_JSON_TOKEN_END_ARRAY, + /** + * An "end of object" '}' token. + */ + CX_JSON_TOKEN_END_OBJECT, + /** + * A colon ':' token separating names and values. + */ + CX_JSON_TOKEN_NAME_SEPARATOR, + /** + * A comma ',' token separating object entries or array elements. + */ + CX_JSON_TOKEN_VALUE_SEPARATOR, + /** + * A string token. + */ + CX_JSON_TOKEN_STRING, + /** + * A number token that can be represented as integer. + */ + CX_JSON_TOKEN_INTEGER, + /** + * A number token that cannot be represented as integer. + */ + CX_JSON_TOKEN_NUMBER, + /** + * A literal token. + */ + CX_JSON_TOKEN_LITERAL, + /** + * A white-space token. + */ + CX_JSON_TOKEN_SPACE +}; + +/** + * The type of some JSON value. + */ +enum cx_json_value_type { + /** + * Reserved. + */ + CX_JSON_NOTHING, // this allows us to always return non-NULL values + /** + * A JSON object. + */ + CX_JSON_OBJECT, + /** + * A JSON array. + */ + CX_JSON_ARRAY, + /** + * A string. + */ + CX_JSON_STRING, + /** + * A number that contains an integer. + */ + CX_JSON_INTEGER, + /** + * A number, not necessarily an integer. + */ + CX_JSON_NUMBER, + /** + * A literal (true, false, null). + */ + CX_JSON_LITERAL +}; + +/** + * JSON literal types. + */ +enum cx_json_literal { + /** + * The @c null literal. + */ + CX_JSON_NULL, + /** + * The @c true literal. + */ + CX_JSON_TRUE, + /** + * The @c false literal. + */ + CX_JSON_FALSE +}; + +/** + * Type alias for the token type enum. + */ +typedef enum cx_json_token_type CxJsonTokenType; +/** + * Type alias for the value type enum. + */ +typedef enum cx_json_value_type CxJsonValueType; + +/** + * Type alias for the JSON parser interface. + */ +typedef struct cx_json_s CxJson; + +/** + * Type alias for the token struct. + */ +typedef struct cx_json_token_s CxJsonToken; + +/** + * Type alias for the JSON value struct. + */ +typedef struct cx_json_value_s CxJsonValue; + +/** + * Type alias for the JSON array struct. + */ +typedef struct cx_json_array_s CxJsonArray; +/** + * Type alias for the JSON object struct. + */ +typedef struct cx_json_object_s CxJsonObject; +/** + * Type alias for a JSON string. + */ +typedef struct cx_mutstr_s CxJsonString; +/** + * Type alias for a number that can be represented as 64-bit signed integer. + */ +typedef int64_t CxJsonInteger; +/** + * Type alias for number that is not an integer. + */ +typedef double CxJsonNumber; +/** + * Type alias for a JSON literal. + */ +typedef enum cx_json_literal CxJsonLiteral; + +/** + * Type alias for a key/value pair in a JSON object. + */ +typedef struct cx_json_obj_value_s CxJsonObjValue; + +/** + * JSON array structure. + */ +struct cx_json_array_s { + /** + * The array data. + */ + CX_ARRAY_DECLARE(CxJsonValue*, array); +}; + +/** + * JSON object structure. + */ +struct cx_json_object_s { + /** + * The key/value entries. + */ + CX_ARRAY_DECLARE(CxJsonObjValue, values); + /** + * The original indices to reconstruct the order in which the members were added. + */ + size_t *indices; +}; + +/** + * Structure for a key/value entry in a JSON object. + */ +struct cx_json_obj_value_s { + /** + * The key (or name in JSON terminology) of the value. + */ + cxmutstr name; + /** + * The value. + */ + CxJsonValue *value; +}; + +/** + * Structure for a JSON value. + */ +struct cx_json_value_s { + /** + * The allocator with which the value was allocated. + * + * Required for recursively deallocating memory of objects and arrays. + */ + const CxAllocator *allocator; + /** + * The type of this value. + * + * Specifies how the @c value union shall be resolved. + */ + CxJsonValueType type; + /** + * The value data. + */ + union { + /** + * The array data if type is #CX_JSON_ARRAY. + */ + CxJsonArray array; + /** + * The object data if type is #CX_JSON_OBJECT. + */ + CxJsonObject object; + /** + * The string data if type is #CX_JSON_STRING. + */ + CxJsonString string; + /** + * The integer if type is #CX_JSON_INTEGER. + */ + CxJsonInteger integer; + /** + * The number if type is #CX_JSON_NUMBER. + */ + CxJsonNumber number; + /** + * The literal type if type is #CX_JSON_LITERAL. + */ + CxJsonLiteral literal; + } value; +}; + +/** + * Internally used structure for a parsed token. + * + * You should never need to use this in your code. + */ +struct cx_json_token_s { + /** + * The token type. + */ + CxJsonTokenType tokentype; + /** + * True, if the @c content must be passed to cx_strfree(). + */ + bool allocated; + /** + * The token text, if any. + * + * This is not necessarily set when the token type already + * uniquely identifies the content. + */ + cxmutstr content; +}; + +/** + * The JSON parser interface. + */ +struct cx_json_s { + /** + * The allocator used for produced JSON values. + */ + const CxAllocator *allocator; + /** + * The input buffer. + */ + CxBuffer buffer; + + /** + * Used internally. + * + * Remembers the prefix of the last uncompleted token. + */ + CxJsonToken uncompleted; + + /** + * A pointer to an intermediate state of the currently parsed value. + * + * Never access this value manually. + */ + CxJsonValue *parsed; + + /** + * A pointer to an intermediate state of a currently parsed object member. + * + * Never access this value manually. + */ + CxJsonObjValue uncompleted_member; + + /** + * State stack. + */ + CX_ARRAY_DECLARE_SIZED(int, states, unsigned); + + /** + * Value buffer stack. + */ + CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned); + + /** + * Internally reserved memory for the state stack. + */ + int states_internal[8]; + + /** + * Internally reserved memory for the value buffer stack. + */ + CxJsonValue* vbuf_internal[8]; +}; + +/** + * Status codes for the json interface. + */ +enum cx_json_status { + /** + * Everything is fine. + */ + CX_JSON_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_JSON_NO_DATA, + /** + * The input ends unexpectedly. + * + * Refill the buffer with cxJsonFill() to complete the json data. + */ + CX_JSON_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_JSON_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_JSON_OK, + /** + * The input buffer has never been filled. + */ + CX_JSON_NULL_DATA, + /** + * Allocating memory for the internal buffer failed. + */ + CX_JSON_BUFFER_ALLOC_FAILED, + /** + * Allocating memory for a json value failed. + */ + CX_JSON_VALUE_ALLOC_FAILED, + /** + * A number value is incorrectly formatted. + */ + CX_JSON_FORMAT_ERROR_NUMBER, + /** + * The tokenizer found something unexpected. + */ + CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN +}; + +/** + * Typedef for the json status enum. + */ +typedef enum cx_json_status CxJsonStatus; + +/** + * The JSON writer settings. + */ +struct cx_json_writer_s { + /** + * Set true to enable pretty output. + */ + bool pretty; + /** + * Set false to output the members in the order in which they were added. + */ + bool sort_members; + /** + * The maximum number of fractional digits in a number value. + * The default value is 6 and values larger than 15 are reduced to 15. + * Note, that the actual number of digits may be lower, depending on the concrete number. + */ + uint8_t frac_max_digits; + /** + * Set true to use spaces instead of tab characters. + * Indentation is only used in pretty output. + */ + bool indent_space; + /** + * If @c indent_space is true, this is the number of spaces per tab. + * Indentation is only used in pretty output. + */ + uint8_t indent; + /** + * Set true to enable escaping of the slash character (solidus). + */ + bool escape_slash; +}; + +/** + * Typedef for the json writer. + */ +typedef struct cx_json_writer_s CxJsonWriter; + +/** + * Creates a default writer configuration for compact output. + * + * @return new JSON writer settings + */ +cx_attr_nodiscard +cx_attr_export +CxJsonWriter cxJsonWriterCompact(void); + +/** + * Creates a default writer configuration for pretty output. + * + * @param use_spaces false if you want tabs, true if you want four spaces instead + * @return new JSON writer settings + */ +cx_attr_nodiscard +cx_attr_export +CxJsonWriter cxJsonWriterPretty(bool use_spaces); + +/** + * Writes a JSON value to a buffer or stream. + * + * This function blocks until either all data is written, or an error occurs. + * The write operation is not atomic in the sense that it might happen + * that the data is only partially written when an error occurs with no + * way to indicate how much data was written. + * To avoid this problem, you can use a CxBuffer as @p target which is + * unlikely to fail a write operation and either use the buffer's flush + * feature to relay the data or use the data in the buffer manually to + * write it to the actual target. + * + * @param target the buffer or stream where to write to + * @param value the value that shall be written + * @param wfunc the write function to use + * @param settings formatting settings (or @c NULL to use a compact default) + * @retval zero success + * @retval non-zero when no or not all data could be written + */ +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_export +int cxJsonWrite( + void* target, + const CxJsonValue* value, + cx_write_func wfunc, + const CxJsonWriter* settings +); + +/** + * Initializes the json interface. + * + * @param json the json interface + * @param allocator the allocator that shall be used for the produced values + * @see cxJsonDestroy() + */ +cx_attr_nonnull_arg(1) +cx_attr_export +void cxJsonInit(CxJson *json, const CxAllocator *allocator); + +/** + * Destroys the json interface. + * + * @param json the json interface + * @see cxJsonInit() + */ +cx_attr_nonnull +cx_attr_export +void cxJsonDestroy(CxJson *json); + +/** + * Destroys and re-initializes the json interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param json the json interface + */ +cx_attr_nonnull +static inline void cxJsonReset(CxJson *json) { + const CxAllocator *allocator = json->allocator; + cxJsonDestroy(json); + cxJsonInit(json, allocator); +} + +/** + * Fills the input buffer. + * + * @remark The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param buf the source buffer + * @param len the length of the source buffer + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonFilln(CxJson *json, const char *buf, size_t len); + +#ifdef __cplusplus +} // extern "C" + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxJsonFill( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer. + * + * The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param str the source string + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFilln() + */ +#define cxJsonFill(json, str) _Generic((str), \ + cxstring: cx_json_fill_cxstr, \ + cxmutstr: cx_json_fill_mutstr, \ + char*: cx_json_fill_str, \ + const char*: cx_json_fill_str) \ + (json, str) + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_cxstr( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_mutstr( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_json_fill_str( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} +#endif + +/** + * Creates a new (empty) JSON object. + * + * @param allocator the allocator to use + * @return the new JSON object or @c NULL if allocation fails + * @see cxJsonObjPutObj() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); + +/** + * Creates a new (empty) JSON array. + * + * @param allocator the allocator to use + * @return the new JSON array or @c NULL if allocation fails + * @see cxJsonObjPutArr() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); + +/** + * Creates a new JSON number value. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutNumber() + * @see cxJsonArrAddNumbers() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); + +/** + * Creates a new JSON number value based on an integer. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutInteger() + * @see cxJsonArrAddIntegers() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateString() + * @see cxJsonObjPutString() + * @see cxJsonArrAddStrings() + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(2) +cx_attr_cstr_arg(2) +cx_attr_export +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateCxString() + * @see cxJsonObjPutCxString() + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); + +/** + * Creates a new JSON literal. + * + * @param allocator the allocator to use + * @param lit the type of literal + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutLiteral() + * @see cxJsonArrAddLiterals() + */ +cx_attr_nodiscard +cx_attr_export +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); + +/** + * Adds number values to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); + +/** + * Adds number values, of which all are integers, to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); + +/** + * Adds literals to a JSON array. + * + * @param arr the JSON array + * @param lit the array of literal types + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); + +/** + * Add arbitrary values to a JSON array. + * + * @attention In contrast to all other add functions, this function adds the values + * directly to the array instead of copying them. + * + * @param arr the JSON array + * @param val the values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); + +/** + * Adds or replaces a value within a JSON object. + * + * The value will be directly added and not copied. + * + * @note If a value with the specified @p name already exists, + * it will be (recursively) freed with its own allocator. + * + * @param obj the JSON object + * @param name the name of the value + * @param child the value + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_export +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); + +/** + * Creates a new JSON object and adds it to an existing object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateObj() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON array and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateArr() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON number and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateNumber() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); + +/** + * Creates a new JSON number, based on an integer, and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateInteger() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateString() + */ +cx_attr_nonnull +cx_attr_cstr_arg(3) +cx_attr_export +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateCxString() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); + +/** + * Creates a new JSON literal and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param lit the type of literal + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateLiteral() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); + +/** + * Recursively deallocates the memory of a JSON value. + * + * @remark The type of each deallocated value will be changed + * to #CX_JSON_NOTHING and values of such type will be skipped + * by the de-allocation. That means, this function protects + * you from double-frees when you are accidentally freeing + * a nested value and then the parent value (or vice versa). + * + * @param value the value + */ +cx_attr_export +void cxJsonValueFree(CxJsonValue *value); + +/** + * Tries to obtain the next JSON value. + * + * Before this function can be called, the input buffer needs + * to be filled with cxJsonFill(). + * + * When this function returns #CX_JSON_INCOMPLETE_DATA, you can + * add the missing data with another invocation of cxJsonFill() + * and then repeat the call to cxJsonNext(). + * + * @param json the json interface + * @param value a pointer where the next value shall be stored + * @retval CX_JSON_NO_ERROR successfully retrieve the @p value + * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from + * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read + * and more data needs to be filled + * @retval CX_JSON_NULL_DATA the buffer was never initialized + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +cx_attr_nonnull +cx_attr_access_w(2) +cx_attr_export +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); + +/** + * Checks if the specified value is a JSON object. + * + * @param value a pointer to the value + * @retval true the value is a JSON object + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsObject(const CxJsonValue *value) { + return value->type == CX_JSON_OBJECT; +} + +/** + * Checks if the specified value is a JSON array. + * + * @param value a pointer to the value + * @retval true the value is a JSON array + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsArray(const CxJsonValue *value) { + return value->type == CX_JSON_ARRAY; +} + +/** + * Checks if the specified value is a string. + * + * @param value a pointer to the value + * @retval true the value is a string + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsString(const CxJsonValue *value) { + return value->type == CX_JSON_STRING; +} + +/** + * Checks if the specified value is a JSON number. + * + * This function will return true for both floating point and + * integer numbers. + * + * @param value a pointer to the value + * @retval true the value is a JSON number + * @retval false otherwise + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline bool cxJsonIsNumber(const CxJsonValue *value) { + return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is an integer number. + * + * @param value a pointer to the value + * @retval true the value is an integer number + * @retval false otherwise + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline bool cxJsonIsInteger(const CxJsonValue *value) { + return value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is a JSON literal. + * + * JSON literals are @c true, @c false, and @c null. + * + * @param value a pointer to the value + * @retval true the value is a JSON literal + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + * @see cxJsonIsNull() + */ +cx_attr_nonnull +static inline bool cxJsonIsLiteral(const CxJsonValue *value) { + return value->type == CX_JSON_LITERAL; +} + +/** + * Checks if the specified value is a Boolean literal. + * + * @param value a pointer to the value + * @retval true the value is either @c true or @c false + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsBool(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; +} + +/** + * Checks if the specified value is @c true. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsFalse(v). + * + * @param value a pointer to the value + * @retval true the value is @c true + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsTrue(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; +} + +/** + * Checks if the specified value is @c false. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsTrue(v). + * + * @param value a pointer to the value + * @retval true the value is @c false + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsTrue() + */ +cx_attr_nonnull +static inline bool cxJsonIsFalse(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; +} + +/** + * Checks if the specified value is @c null. + * + * @param value a pointer to the value + * @retval true the value is @c null + * @retval false otherwise + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonIsNull(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; +} + +/** + * Obtains a C string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as C string + * @see cxJsonIsString() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline char *cxJsonAsString(const CxJsonValue *value) { + return value->value.string.ptr; +} + +/** + * Obtains a UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { + return cx_strcast(value->value.string); +} + +/** + * Obtains a mutable UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as mutable UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { + return value->value.string; +} + +/** + * Obtains a double-precision floating point value from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline double cxJsonAsDouble(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return (double) value->value.integer; + } else { + return value->value.number; + } +} + +/** + * Obtains a 64-bit signed integer from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * If it is a JSON number, but not an integer, the value will be + * converted to an integer, possibly losing precision. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return value->value.integer; + } else { + return (int64_t) value->value.number; + } +} + +/** + * Obtains a Boolean value from the given JSON value. + * + * If the @p value is not a JSON literal, the behavior is undefined. + * The @c null literal is interpreted as @c false. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonAsBool(const CxJsonValue *value) { + return value->value.literal == CX_JSON_TRUE; +} + +/** + * Returns the size of a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return the size of the array + * @see cxJsonIsArray() + */ +cx_attr_nonnull +static inline size_t cxJsonArrSize(const CxJsonValue *value) { + return value->value.array.array_size; +} + +/** + * Returns an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function guarantees to return a value. If the index is + * out of bounds, the returned value will be of type + * #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON value + * @param index the index in the array + * @return the value at the specified index + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); + +/** + * Returns an iterator over the JSON array elements. + * + * The iterator yields values of type @c CxJsonValue* . + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the array elements + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxIterator cxJsonArrIter(const CxJsonValue *value); + +/** + * Returns an iterator over the JSON object members. + * + * The iterator yields values of type @c CxJsonObjValue* which + * contain the name and value of the member. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the object members + * @see cxJsonIsObject() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxIterator cxJsonObjIter(const CxJsonValue *value); + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); + +#ifdef __cplusplus +} // extern "C" + +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { + return cx_json_obj_get_cxstr(value, name); +} + +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} + +extern "C" { +#else +/** + * Returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function guarantees to return a JSON value. If the + * object does not contain @p name, the returned JSON value + * will be of type #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key + * @see cxJsonIsObject() + */ +#define cxJsonObjGet(value, name) _Generic((name), \ + cxstring: cx_json_obj_get_cxstr, \ + cxmutstr: cx_json_obj_get_mutstr, \ + char*: cx_json_obj_get_str, \ + const char*: cx_json_obj_get_str) \ + (value, name) + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_cstr_arg(2) +static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_JSON_H */ +
--- a/src/ucx/cx/linked_list.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/linked_list.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,13 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file linked_list.h - * \brief Linked list implementation. - * \details Also provides several low-level functions for custom linked list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file linked_list.h + * @brief Linked list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_LINKED_LIST_H @@ -46,55 +44,56 @@ #endif /** - * Set this flag to true, if you want to disable the use of SBO for - * linked list swap operations. - */ -extern bool CX_DISABLE_LINKED_LIST_SWAP_SBO; - -/** - * Allocates a linked list for storing elements with \p item_size bytes each. + * Allocates a linked list for storing elements with @p elem_size bytes each. * - * If \p item_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements and the compare function will be automatically set + * to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list nodes - * (if \c NULL the cxDefaultAllocator will be used) + * (if @c NULL, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL sort and find functions will not work) - * @param item_size the size of each element in bytes + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) +cx_attr_export CxList *cxLinkedListCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, cx_compare_func comparator, - size_t item_size + size_t elem_size ); /** - * Allocates a linked list for storing elements with \p item_size bytes each. + * Allocates a linked list for storing elements with @p elem_size bytes each. * * The list will use cxDefaultAllocator and no comparator function. If you want * to call functions that need a comparator, you must either set one immediately * after list creation or use cxLinkedListCreate(). * - * If \p item_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation. + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements and the compare function will be automatically set + * to cx_cmp_ptr(), if none is given. * - * @param item_size the size of each element in bytes - * @return the created list + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list */ -#define cxLinkedListCreateSimple(item_size) \ - cxLinkedListCreate(NULL, NULL, item_size) +#define cxLinkedListCreateSimple(elem_size) \ + cxLinkedListCreate(NULL, NULL, elem_size) /** * Finds the node at a certain index. * * This function can be used to start at an arbitrary position within the list. - * If the search index is large than the start index, \p loc_advance must denote - * the location of some sort of \c next pointer (i.e. a pointer to the next node). + * If the search index is large than the start index, @p loc_advance must denote + * the location of some sort of @c next pointer (i.e. a pointer to the next node). * But it is also possible that the search index is smaller than the start index * (e.g. in cases where traversing a list backwards is faster) in which case - * \p loc_advance must denote the location of some sort of \c prev pointer + * @p loc_advance must denote the location of some sort of @c prev pointer * (i.e. a pointer to the previous node). * * @param start a pointer to the start node @@ -103,165 +102,191 @@ * @param index the search index * @return the node found at the specified index */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export void *cx_linked_list_at( - void const *start, + const void *start, size_t start_index, ptrdiff_t loc_advance, size_t index -) __attribute__((__nonnull__)); +); /** - * Finds the index of an element within a linked list. + * Finds the node containing an element within a linked list. * * @param start a pointer to the start node * @param loc_advance the location of the pointer to advance - * @param loc_data the location of the \c data pointer within your node struct - * @param cmp_func a compare function to compare \p elem against the node data + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func a compare function to compare @p elem against the node data * @param elem a pointer to the element to find - * @return the index of the element or a negative value if it could not be found + * @param found_index an optional pointer where the index of the found node + * (given that @p start has index 0) is stored + * @return the index of the element, if found - unspecified if not found */ -ssize_t cx_linked_list_find( - void const *start, +cx_attr_nonnull_arg(1, 4, 5) +cx_attr_export +void *cx_linked_list_find( + const void *start, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func, - void const *elem -) __attribute__((__nonnull__)); + const void *elem, + size_t *found_index +); /** * Finds the first node in a linked list. * - * The function starts with the pointer denoted by \p node and traverses the list + * The function starts with the pointer denoted by @p node and traverses the list * along a prev pointer whose location within the node struct is - * denoted by \p loc_prev. + * denoted by @p loc_prev. * * @param node a pointer to a node in the list - * @param loc_prev the location of the \c prev pointer + * @param loc_prev the location of the @c prev pointer * @return a pointer to the first node */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export void *cx_linked_list_first( - void const *node, + const void *node, ptrdiff_t loc_prev -) __attribute__((__nonnull__)); +); /** * Finds the last node in a linked list. * - * The function starts with the pointer denoted by \p node and traverses the list + * The function starts with the pointer denoted by @p node and traverses the list * along a next pointer whose location within the node struct is - * denoted by \p loc_next. + * denoted by @p loc_next. * * @param node a pointer to a node in the list - * @param loc_next the location of the \c next pointer + * @param loc_next the location of the @c next pointer * @return a pointer to the last node */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export void *cx_linked_list_last( - void const *node, + const void *node, ptrdiff_t loc_next -) __attribute__((__nonnull__)); +); /** * Finds the predecessor of a node in case it is not linked. * - * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined. + * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined. * * @param begin the node where to start the search - * @param loc_next the location of the \c next pointer + * @param loc_next the location of the @c next pointer * @param node the successor of the node to find - * @return the node or \c NULL if \p node has no predecessor + * @return the node or @c NULL if @p node has no predecessor */ +cx_attr_nonnull +cx_attr_export void *cx_linked_list_prev( - void const *begin, + const void *begin, ptrdiff_t loc_next, - void const *node -) __attribute__((__nonnull__)); + const void *node +); /** * Adds a new node to a linked list. * The node must not be part of any list already. * - * \remark One of the pointers \p begin or \p end may be \c NULL, but not both. + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be appended */ +cx_attr_nonnull_arg(5) +cx_attr_export void cx_linked_list_add( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node -) __attribute__((__nonnull__(5))); +); /** * Prepends a new node to a linked list. * The node must not be part of any list already. * - * \remark One of the pointers \p begin or \p end may be \c NULL, but not both. + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be prepended */ +cx_attr_nonnull_arg(5) +cx_attr_export void cx_linked_list_prepend( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node -) __attribute__((__nonnull__(5))); +); /** * Links two nodes. * - * @param left the new predecessor of \p right - * @param right the new successor of \p left - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param left the new predecessor of @p right + * @param right the new successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ +cx_attr_nonnull +cx_attr_export void cx_linked_list_link( void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next -) __attribute__((__nonnull__)); +); /** * Unlinks two nodes. * * If right is not the successor of left, the behavior is undefined. * - * @param left the predecessor of \p right - * @param right the successor of \p left - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param left the predecessor of @p right + * @param right the successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ +cx_attr_nonnull +cx_attr_export void cx_linked_list_unlink( void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next -) __attribute__((__nonnull__)); +); /** * Inserts a new node after a given node of a linked list. * The new node must not be part of any list already. * - * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or - * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list. + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param node the node after which to insert (\c NULL if you want to prepend the node to the list) - * @param new_node a pointer to the node that shall be prepended + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL if you want to prepend the node to the list) + * @param new_node a pointer to the node that shall be inserted */ +cx_attr_nonnull_arg(6) +cx_attr_export void cx_linked_list_insert( void **begin, void **end, @@ -269,28 +294,30 @@ ptrdiff_t loc_next, void *node, void *new_node -) __attribute__((__nonnull__(6))); +); /** * Inserts a chain of nodes after a given node of a linked list. * The chain must not be part of any list already. * * If you do not explicitly specify the end of the chain, it will be determined by traversing - * the \c next pointer. + * the @c next pointer. * - * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or - * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need - * to provide a valid \p loc_prev location. + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need + * to provide a valid @p loc_prev location. * Then the chain will be prepended to the list. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param node the node after which to insert (\c NULL to prepend the chain to the list) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL to prepend the chain to the list) * @param insert_begin a pointer to the first node of the chain that shall be inserted * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ +cx_attr_nonnull_arg(6) +cx_attr_export void cx_linked_list_insert_chain( void **begin, void **end, @@ -299,44 +326,136 @@ void *node, void *insert_begin, void *insert_end -) __attribute__((__nonnull__(6))); +); + +/** + * Inserts a node into a sorted linked list. + * The new node must not be part of any list already. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + */ +cx_attr_nonnull_arg(1, 5, 6) +cx_attr_export +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +); + +/** + * Inserts a chain of nodes into a sorted linked list. + * The chain must not be part of any list already. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_chain(), the source chain + * will be broken and inserted into the target list so that the resulting list + * will be sorted according to @p cmp_func. That means, each node in the source + * chain may be re-linked with nodes from the target list. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + */ +cx_attr_nonnull_arg(1, 5, 6) +cx_attr_export +void cx_linked_list_insert_sorted_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +); + +/** + * Removes a chain of nodes from the linked list. + * + * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * addresses are provided, the pointers are adjusted accordingly. + * + * The following combinations of arguments are valid (more arguments are optional): + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) + * + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used + * to traverse to a former adjacent node in the list, or within the chain. + * + * @param begin a pointer to the beginning node pointer (optional) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the start node of the chain + * @param num the number of nodes to remove + * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) + */ +cx_attr_nonnull_arg(5) +cx_attr_export +size_t cx_linked_list_remove_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + size_t num +); /** * Removes a node from the linked list. * - * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end) + * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): - * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) - * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance) + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) * - * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used * to traverse to a former adjacent node in the list. * - * @param begin a pointer to the begin node pointer (optional) + * @param begin a pointer to the beginning node pointer (optional) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param node the node to remove */ -void cx_linked_list_remove( +cx_attr_nonnull_arg(5) +static inline void cx_linked_list_remove( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node -) __attribute__((__nonnull__(5))); - +) { + cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); +} /** - * Determines the size of a linked list starting with \p node. + * Determines the size of a linked list starting with @p node. + * * @param node the first node - * @param loc_next the location of the \c next pointer within the node struct - * @return the size of the list or zero if \p node is \c NULL + * @param loc_next the location of the @c next pointer within the node struct + * @return the size of the list or zero if @p node is @c NULL */ +cx_attr_nodiscard +cx_attr_export size_t cx_linked_list_size( - void const *node, + const void *node, ptrdiff_t loc_next ); @@ -344,24 +463,26 @@ * Sorts a linked list based on a comparison function. * * This function can work with linked lists of the following structure: - * \code + * @code * typedef struct node node; * struct node { * node* prev; * node* next; * my_payload data; * } - * \endcode + * @endcode * * @note This is a recursive function with at most logarithmic recursion depth. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param loc_data the location of the \c data pointer within your node struct + * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param loc_data the location of the @c data pointer within your node struct * @param cmp_func the compare function defining the sort order */ +cx_attr_nonnull_arg(1, 6) +cx_attr_export void cx_linked_list_sort( void **begin, void **end, @@ -369,44 +490,48 @@ ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func -) __attribute__((__nonnull__(1, 6))); +); /** * Compares two lists element wise. * - * \note Both list must have the same structure. + * @attention Both list must have the same structure. * - * @param begin_left the begin of the left list (\c NULL denotes an empty list) - * @param begin_right the begin of the right list (\c NULL denotes an empty list) + * @param begin_left the beginning of the left list (@c NULL denotes an empty list) + * @param begin_right the beginning of the right list (@c NULL denotes an empty list) * @param loc_advance the location of the pointer to advance - * @param loc_data the location of the \c data pointer within your node struct + * @param loc_data the location of the @c data pointer within your node struct * @param cmp_func the function to compare the elements - * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the + * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the * right list, positive if the left list is larger than the right list, zero if both lists are equal. */ +cx_attr_nonnull_arg(5) +cx_attr_export int cx_linked_list_compare( - void const *begin_left, - void const *begin_right, + const void *begin_left, + const void *begin_right, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func -) __attribute__((__nonnull__(5))); +); /** * Reverses the order of the nodes in a linked list. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ +cx_attr_nonnull_arg(1) +cx_attr_export void cx_linked_list_reverse( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next -) __attribute__((__nonnull__(1))); +); #ifdef __cplusplus } // extern "C"
--- a/src/ucx/cx/list.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/list.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file list.h - * \brief Interface for list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file list.h + * @brief Interface for list implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_LIST_H @@ -53,15 +52,18 @@ * Structure for holding the base data of a list. */ struct cx_list_s { - CX_COLLECTION_MEMBERS + /** + * Common members for collections. + */ + CX_COLLECTION_BASE; /** * The list class definition. */ - cx_list_class const *cl; + const cx_list_class *cl; /** * The actual implementation in case the list class is delegating. */ - cx_list_class const *climpl; + const cx_list_class *climpl; }; /** @@ -72,28 +74,39 @@ * Destructor function. * * Implementations SHALL invoke the content destructor functions if provided - * and SHALL deallocate the list memory, if an allocator is provided. + * and SHALL deallocate the entire list memory. */ - void (*destructor)(struct cx_list_s *list); + void (*deallocate)(struct cx_list_s *list); /** * Member function for inserting a single element. - * Implementors SHOULD see to performant implementations for corner cases. */ int (*insert_element)( struct cx_list_s *list, size_t index, - void const *data + const void *data ); /** * Member function for inserting multiple elements. - * Implementors SHOULD see to performant implementations for corner cases. + * + * @see cx_list_default_insert_array() */ size_t (*insert_array)( struct cx_list_s *list, size_t index, - void const *data, + const void *data, + size_t n + ); + + /** + * Member function for inserting sorted elements into a sorted list. + * + * @see cx_list_default_insert_sorted() + */ + size_t (*insert_sorted)( + struct cx_list_s *list, + const void *sorted_data, size_t n ); @@ -101,17 +114,26 @@ * Member function for inserting an element relative to an iterator position. */ int (*insert_iter)( - struct cx_mut_iterator_s *iter, - void const *elem, + struct cx_iterator_s *iter, + const void *elem, int prepend ); /** - * Member function for removing an element. + * Member function for removing elements. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return the actual number of elements removed, which + * might be lower than @p num when going out of bounds. */ - int (*remove)( + size_t (*remove)( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ); /** @@ -121,6 +143,8 @@ /** * Member function for swapping two elements. + * + * @see cx_list_default_swap() */ int (*swap)( struct cx_list_s *list, @@ -132,29 +156,35 @@ * Member function for element lookup. */ void *(*at)( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index ); /** - * Member function for finding an element. + * Member function for finding and optionally removing an element. */ - ssize_t (*find)( - struct cx_list_s const *list, - void const *elem + size_t (*find_remove)( + struct cx_list_s *list, + const void *elem, + bool remove ); /** - * Member function for sorting the list in-place. + * Member function for sorting the list. + * + * @see cx_list_default_sort() */ void (*sort)(struct cx_list_s *list); /** - * Member function for comparing this list to another list of the same type. + * Optional member function for comparing this list + * to another list of the same type. + * If set to @c NULL, comparison won't be optimized. */ + cx_attr_nonnull int (*compare)( - struct cx_list_s const *list, - struct cx_list_s const *other + const struct cx_list_s *list, + const struct cx_list_s *other ); /** @@ -166,65 +196,159 @@ * Member function for returning an iterator pointing to the specified index. */ struct cx_iterator_s (*iterator)( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index, bool backward ); }; /** + * Default implementation of an array insert. + * + * This function uses the element insert function for each element of the array. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list + * @param index the index where to insert the data + * @param data a pointer to the array of data to insert + * @param n the number of elements to insert + * @return the number of elements actually inserted + */ +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 +); + +/** + * Default implementation of a sorted insert. + * + * This function uses the array insert function to insert consecutive groups + * of sorted data. + * + * The source data @em must already be sorted wrt. the list's compare function. + * + * 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 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 +); + +/** + * Default unoptimized sort implementation. + * + * This function will copy all data to an array, sort the array with standard + * qsort, and then copy the data back to the list memory. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list that shall be sorted + */ +cx_attr_nonnull +cx_attr_export +void cx_list_default_sort(struct cx_list_s *list); + +/** + * Default unoptimized swap implementation. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list in which to swap + * @param i index of one element + * @param j index of the other element + * @retval zero success + * @retval non-zero when indices are out of bounds or memory + * allocation for the temporary buffer fails + */ +cx_attr_nonnull +cx_attr_export +int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); + +/** + * Initializes a list struct. + * + * Only use this function if you are creating your own list implementation. + * The purpose of this function is to be called in the initialization code + * of your list, to set certain members correctly. + * + * This is particularly important when you want your list to support + * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list + * class accordingly and make sure that you can implement your list as if + * it was only storing objects and the wrapper will automatically enable + * the feature of storing pointers. + * + * @par Example + * + * @code + * CxList *myCustomListCreate( + * const CxAllocator *allocator, + * cx_compare_func comparator, + * size_t elem_size + * ) { + * if (allocator == NULL) { + * allocator = cxDefaultAllocator; + * } + * + * MyCustomList *list = cxCalloc(allocator, 1, sizeof(MyCustomList)); + * if (list == NULL) return NULL; + * + * // initialize + * cx_list_init((CxList*)list, &my_custom_list_class, + * allocator, comparator, elem_size); + * + * // ... some more custom stuff ... + * + * return (CxList *) list; + * } + * @endcode + * + * @param list the list to initialize + * @param cl the list class + * @param allocator the allocator for the elements + * @param comparator a compare function for the elements + * @param elem_size the size of one element + */ +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_export +void cx_list_init( + struct cx_list_s *list, + struct cx_list_class_s *cl, + const struct cx_allocator_s *allocator, + cx_compare_func comparator, + size_t elem_size +); + +/** * Common type for all list implementations. */ typedef struct cx_list_s CxList; /** - * Advises the list to store copies of the objects (default mode of operation). - * - * Retrieving objects from this list will yield pointers to the copies stored - * within this list. - * - * @param list the list - * @see cxListStorePointers() - */ -__attribute__((__nonnull__)) -void cxListStoreObjects(CxList *list); - -/** - * Advises the list to only store pointers to the objects. - * - * Retrieving objects from this list will yield the original pointers stored. - * - * @note This function forcibly sets the element size to the size of a pointer. - * Invoking this function on a non-empty list that already stores copies of - * objects is undefined. - * - * @param list the list - * @see cxListStoreObjects() - */ -__attribute__((__nonnull__)) -void cxListStorePointers(CxList *list); - -/** - * Returns true, if this list is storing pointers instead of the actual data. - * - * @param list - * @return true, if this list is storing pointers - * @see cxListStorePointers() - */ -__attribute__((__nonnull__)) -static inline bool cxListIsStoringPointers(CxList const *list) { - return list->store_pointer; -} - -/** * Returns the number of elements currently stored in the list. * * @param list the list * @return the number of currently stored elements */ -__attribute__((__nonnull__)) -static inline size_t cxListSize(CxList const *list) { - return list->size; +cx_attr_nonnull +static inline size_t cxListSize(const CxList *list) { + return list->collection.size; } /** @@ -232,15 +356,17 @@ * * @param list the list * @param elem a pointer to the element to add - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListAddArray() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListAdd( CxList *list, - void const *elem + const void *elem ) { - return list->cl->insert_element(list, list->size, elem); + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, elem); } /** @@ -249,9 +375,9 @@ * This method is more efficient than invoking cxListAdd() multiple times. * * If there is not enough memory to add all elements, the returned value is - * less than \p n. + * less than @p n. * - * If this list is storing pointers instead of objects \p array is expected to + * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * * @param list the list @@ -259,48 +385,70 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListAddArray( CxList *list, - void const *array, + const void *array, size_t n ) { - return list->cl->insert_array(list, list->size, array, n); + list->collection.sorted = false; + return list->cl->insert_array(list, list->collection.size, array, n); } /** * Inserts an item at the specified index. * - * If \p index equals the list \c size, this is effectively cxListAdd(). + * If @p index equals the list @c size, this is effectively cxListAdd(). * * @param list the list * @param index the index the element shall have * @param elem a pointer to the element to add - * @return zero on success, non-zero on memory allocation failure - * or when the index is out of bounds + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds * @see cxListInsertAfter() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsert( CxList *list, size_t index, - void const *elem + const void *elem ) { + list->collection.sorted = false; return list->cl->insert_element(list, index, elem); } /** + * Inserts an item into a sorted list. + * + * If the list is not sorted already, the behavior is undefined. + * + * @param list the list + * @param elem a pointer to the element to add + * @retval zero success + * @retval non-zero memory allocation failure + */ +cx_attr_nonnull +static inline int cxListInsertSorted( + CxList *list, + const void *elem +) { + list->collection.sorted = true; // guaranteed by definition + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_sorted(list, data, 1) == 0; +} + +/** * Inserts multiple items to the list at the specified index. - * If \p index equals the list size, this is effectively cxListAddArray(). + * If @p index equals the list size, this is effectively cxListAddArray(). * * This method is usually more efficient than invoking cxListInsert() * multiple times. * * If there is not enough memory to add all elements, the returned value is - * less than \p n. + * less than @p n. * - * If this list is storing pointers instead of objects \p array is expected to + * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * * @param list the list @@ -309,37 +457,70 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertArray( CxList *list, size_t index, - void const *array, + const void *array, size_t n ) { + list->collection.sorted = false; return list->cl->insert_array(list, index, array, n); } /** + * Inserts a sorted array into a sorted list. + * + * This method is usually more efficient than inserting each element separately, + * because consecutive chunks of sorted data are inserted in one pass. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * If this list is storing pointers instead of objects @p array is expected to + * be an array of pointers. + * + * If the list is not sorted already, the behavior is undefined. + * + * @param list the list + * @param array a pointer to the elements to add + * @param n the number of elements to add + * @return the number of added elements + */ +cx_attr_nonnull +static inline size_t cxListInsertSortedArray( + CxList *list, + const void *array, + size_t n +) { + list->collection.sorted = true; // guaranteed by definition + return list->cl->insert_sorted(list, array, n); +} + +/** * Inserts an element after the current location of the specified iterator. * * The used iterator remains operational, but all other active iterators should * be considered invalidated. * - * If \p iter is not a list iterator, the behavior is undefined. - * If \p iter is a past-the-end iterator, the new element gets appended to the list. + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. * * @param iter an iterator * @param elem the element to insert - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListInsert() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertAfter( - CxMutIterator *iter, - void const *elem + CxIterator *iter, + const void *elem ) { - return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 0); + CxList* list = (CxList*)iter->src_handle.m; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 0); } /** @@ -348,21 +529,24 @@ * The used iterator remains operational, but all other active iterators should * be considered invalidated. * - * If \p iter is not a list iterator, the behavior is undefined. - * If \p iter is a past-the-end iterator, the new element gets appended to the list. + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. * * @param iter an iterator * @param elem the element to insert - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListInsert() * @see cxListInsertAfter() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertBefore( - CxMutIterator *iter, - void const *elem + CxIterator *iter, + const void *elem ) { - return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 1); + CxList* list = (CxList*)iter->src_handle.m; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 1); } /** @@ -373,26 +557,97 @@ * * @param list the list * @param index the index of the element - * @return zero on success, non-zero if the index is out of bounds + * @retval zero success + * @retval non-zero index out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListRemove( CxList *list, size_t index ) { - return list->cl->remove(list, index); + return list->cl->remove(list, index, 1, NULL) == 0; +} + +/** + * Removes and returns the element at the specified index. + * + * No destructor is called and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * + * @param list the list + * @param index the index of the element + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero index out of bounds + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxListRemoveAndGet( + CxList *list, + size_t index, + void *targetbuf +) { + return list->cl->remove(list, index, 1, targetbuf) == 0; +} + +/** + * Removes multiple element starting at the specified index. + * + * If an element destructor function is specified, it is called for each + * element. It is guaranteed that the destructor is called before removing + * the element, however, due to possible optimizations it is neither guaranteed + * that the destructors are invoked for all elements before starting to remove + * them, nor that the element is removed immediately after the destructor call + * before proceeding to the next element. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @return the actual number of removed elements + */ +cx_attr_nonnull +static inline size_t cxListRemoveArray( + CxList *list, + size_t index, + size_t num +) { + return list->cl->remove(list, index, num, NULL); +} + +/** + * Removes and returns multiple element starting at the specified index. + * + * No destructor is called and instead the elements are copied to the + * @p targetbuf which MUST be large enough to hold all removed elements. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @param targetbuf a buffer where to copy the elements + * @return the actual number of removed elements + */ +cx_attr_nonnull +cx_attr_access_w(4) +static inline size_t cxListRemoveArrayAndGet( + CxList *list, + size_t index, + size_t num, + void *targetbuf +) { + return list->cl->remove(list, index, num, targetbuf); } /** * Removes all elements from this list. * - * If an element destructor function is specified, it is called for each + * If element destructor functions are specified, they are called for each * element before removing them. * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListClear(CxList *list) { + list->collection.sorted = true; // empty lists are always sorted list->cl->clear(list); } @@ -405,14 +660,17 @@ * @param list the list * @param i the index of the first element * @param j the index of the second element - * @return zero on success, non-zero if one of the indices is out of bounds + * @retval zero success + * @retval non-zero one of the indices is out of bounds + * or the swap needed extra memory but allocation failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListSwap( CxList *list, size_t i, size_t j ) { + list->collection.sorted = false; return list->cl->swap(list, i, j); } @@ -421,11 +679,11 @@ * * @param list the list * @param index the index of the element - * @return a pointer to the element or \c NULL if the index is out of bounds + * @return a pointer to the element or @c NULL if the index is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void *cxListAt( - CxList *list, + const CxList *list, size_t index ) { return list->cl->at(list, index); @@ -442,9 +700,10 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIteratorAt( - CxList const *list, + const CxList *list, size_t index ) { return list->cl->iterator(list, index, false); @@ -461,9 +720,10 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIteratorAt( - CxList const *list, + const CxList *list, size_t index ) { return list->cl->iterator(list, index, true); @@ -480,8 +740,10 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxMutIterator cxListMutIteratorAt( +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxIterator cxListMutIteratorAt( CxList *list, size_t index ); @@ -498,8 +760,10 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxMutIterator cxListMutBackwardsIteratorAt( +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index ); @@ -514,8 +778,9 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxIterator cxListIterator(CxList const *list) { +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListIterator(const CxList *list) { return list->cl->iterator(list, 0, false); } @@ -529,8 +794,9 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxMutIterator cxListMutIterator(CxList *list) { +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListMutIterator(CxList *list) { return cxListMutIteratorAt(list, 0); } @@ -545,9 +811,10 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxIterator cxListBackwardsIterator(CxList const *list) { - return list->cl->iterator(list, list->size - 1, true); +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListBackwardsIterator(const CxList *list) { + return list->cl->iterator(list, list->collection.size - 1, true); } /** @@ -560,39 +827,75 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxMutIterator cxListMutBackwardsIterator(CxList *list) { - return cxListMutBackwardsIteratorAt(list, list->size - 1); +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListMutBackwardsIterator(CxList *list) { + return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); } /** - * Returns the index of the first element that equals \p elem. + * Returns the index of the first element that equals @p elem. * * Determining equality is performed by the list's comparator function. * * @param list the list * @param elem the element to find - * @return the index of the element or a negative - * value when the element is not found + * @return the index of the element or the size of the list when the element is not found + * @see cxListIndexValid() */ -__attribute__((__nonnull__)) -static inline ssize_t cxListFind( - CxList const *list, - void const *elem +cx_attr_nonnull +cx_attr_nodiscard +static inline size_t cxListFind( + const CxList *list, + const void *elem ) { - return list->cl->find(list, elem); + return list->cl->find_remove((CxList*)list, elem, false); +} + +/** + * Checks if the specified index is within bounds. + * + * @param list the list + * @param index the index + * @retval true if the index is within bounds + * @retval false if the index is out of bounds + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline bool cxListIndexValid(const CxList *list, size_t index) { + return index < list->collection.size; } /** - * Sorts the list in-place. + * Removes and returns the index of the first element that equals @p elem. + * + * Determining equality is performed by the list's comparator function. * - * \remark The underlying sort algorithm is implementation defined. + * @param list the list + * @param elem the element to find and remove + * @return the index of the now removed element or the list size + * when the element is not found or could not be removed + * @see cxListIndexValid() + */ +cx_attr_nonnull +static inline size_t cxListFindRemove( + CxList *list, + const void *elem +) { + return list->cl->find_remove(list, elem, true); +} + +/** + * Sorts the list. + * + * @remark The underlying sort algorithm is implementation defined. * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListSort(CxList *list) { list->cl->sort(list); + list->collection.sorted = true; } /** @@ -600,8 +903,10 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListReverse(CxList *list) { + // still sorted, but not according to the cmp_func + list->collection.sorted = false; list->cl->reverse(list); } @@ -613,34 +918,40 @@ * * @param list the list * @param other the list to compare to - * @return zero, if both lists are equal element wise, - * negative if the first list is smaller, positive if the first list is larger + * @retval zero both lists are equal element wise + * @retval negative the first list is smaller + * or the first non-equal element in the first list is smaller + * @retval positive the first list is larger + * or the first non-equal element in the first list is larger */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export int cxListCompare( - CxList const *list, - CxList const *other + const CxList *list, + const CxList *other ); /** * Deallocates the memory of the specified list structure. * - * Also calls content a destructor function, depending on the configuration - * in CxList.content_destructor_type. - * - * This function itself is a destructor function for the CxList. + * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be destroyed + * @param list the list which shall be freed */ -__attribute__((__nonnull__)) -void cxListDestroy(CxList *list); +cx_attr_export +void cxListFree(CxList *list); /** * A shared instance of an empty list. * - * Writing to that list is undefined. + * Writing to that list is not allowed. + * + * You can use this is a placeholder for initializing CxList pointers + * for which you do not want to reserve memory right from the beginning. */ -extern CxList * const cxEmptyList; +cx_attr_export +extern CxList *const cxEmptyList; #ifdef __cplusplus
--- a/src/ucx/cx/map.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/map.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file map.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file map.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_MAP_H @@ -52,17 +51,37 @@ /** Type for a map entry. */ typedef struct cx_map_entry_s CxMapEntry; +/** Type for a map iterator. */ +typedef struct cx_map_iterator_s CxMapIterator; + /** Type for map class definitions. */ typedef struct cx_map_class_s cx_map_class; /** Structure for the UCX map. */ struct cx_map_s { - CX_COLLECTION_MEMBERS + /** + * Base attributes. + */ + CX_COLLECTION_BASE; /** The map class definition. */ cx_map_class *cl; }; /** + * A map entry. + */ +struct cx_map_entry_s { + /** + * A pointer to the key. + */ + const CxHashKey *key; + /** + * A pointer to the value. + */ + void *value; +}; + +/** * The type of iterator for a map. */ enum cx_map_iterator_type { @@ -81,25 +100,92 @@ }; /** + * Internal iterator struct - use CxMapIterator. + */ +struct cx_map_iterator_s { + /** + * Inherited common data for all iterators. + */ + CX_ITERATOR_BASE; + + /** + * Handle for the source map. + */ + union { + /** + * Access for mutating iterators. + */ + CxMap *m; + /** + * Access for normal iterators. + */ + const CxMap *c; + } map; + + /** + * Handle for the current element. + * + * @attention Depends on the map implementation, do not assume a type (better: do not use!). + */ + void *elem; + + /** + * Reserved memory for a map entry. + * + * If a map implementation uses an incompatible layout, the iterator needs something + * to point to during iteration which @em is compatible. + */ + CxMapEntry entry; + + /** + * Field for storing the current slot number. + * + * (Used internally) + */ + size_t slot; + + /** + * Counts the elements successfully. + * It usually does not denote a stable index within the map as it would be for arrays. + */ + size_t index; + + /** + * The size of a value stored in this map. + */ + size_t elem_size; + + /** + * May contain the total number of elements, if known. + * Set to @c SIZE_MAX when the total number is unknown during iteration. + * + * @remark The UCX implementations of #CxMap always know the number of elements they store. + */ + size_t elem_count; + + /** + * The type of this iterator. + */ + enum cx_map_iterator_type type; +}; + +/** * The class definition for arbitrary maps. */ struct cx_map_class_s { /** * Deallocates the entire memory. */ - __attribute__((__nonnull__)) - void (*destructor)(struct cx_map_s *map); + void (*deallocate)(struct cx_map_s *map); /** * Removes all elements. */ - __attribute__((__nonnull__)) void (*clear)(struct cx_map_s *map); /** * Add or overwrite an element. */ - __attribute__((__nonnull__)) int (*put)( CxMap *map, CxHashKey key, @@ -109,143 +195,122 @@ /** * Returns an element. */ - __attribute__((__nonnull__, __warn_unused_result__)) void *(*get)( - CxMap const *map, + const CxMap *map, CxHashKey key ); /** * Removes an element. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return zero when the @p key was found and + * non-zero, otherwise. */ - __attribute__((__nonnull__)) - void *(*remove)( + int (*remove)( CxMap *map, CxHashKey key, - bool destroy + void *targetbuf ); /** * Creates an iterator for this map. */ - __attribute__((__nonnull__, __warn_unused_result__)) - CxIterator (*iterator)(CxMap const *map, enum cx_map_iterator_type type); -}; - -/** - * A map entry. - */ -struct cx_map_entry_s { - /** - * A pointer to the key. - */ - CxHashKey const *key; - /** - * A pointer to the value. - */ - void *value; + CxMapIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); }; /** * A shared instance of an empty map. * - * Writing to that map is undefined. - */ -extern CxMap *const cxEmptyMap; - -/** - * Advises the map to store copies of the objects (default mode of operation). + * Writing to that map is not allowed. * - * Retrieving objects from this map will yield pointers to the copies stored - * within this list. - * - * @param map the map - * @see cxMapStorePointers() + * You can use this is a placeholder for initializing CxMap pointers + * for which you do not want to reserve memory right from the beginning. */ -__attribute__((__nonnull__)) -static inline void cxMapStoreObjects(CxMap *map) { - map->store_pointer = false; -} - -/** - * Advises the map to only store pointers to the objects. - * - * Retrieving objects from this list will yield the original pointers stored. - * - * @note This function forcibly sets the element size to the size of a pointer. - * Invoking this function on a non-empty map that already stores copies of - * objects is undefined. - * - * @param map the map - * @see cxMapStoreObjects() - */ -__attribute__((__nonnull__)) -static inline void cxMapStorePointers(CxMap *map) { - map->store_pointer = true; - map->item_size = sizeof(void *); -} - +cx_attr_export +extern CxMap *const cxEmptyMap; /** * Deallocates the memory of the specified map. * - * @param map the map to be destroyed + * Also calls the content destructor functions for each element, if specified. + * + * @param map the map to be freed */ -__attribute__((__nonnull__)) -static inline void cxMapDestroy(CxMap *map) { - map->cl->destructor(map); -} +cx_attr_export +void cxMapFree(CxMap *map); /** * Clears a map by removing all elements. * + * Also calls the content destructor functions for each element, if specified. + * * @param map the map to be cleared */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapClear(CxMap *map) { map->cl->clear(map); } - -// TODO: set-like map operations (union, intersect, difference) +/** + * Returns the number of elements in this map. + * + * @param map the map + * @return the number of stored elements + */ +cx_attr_nonnull +static inline size_t cxMapSize(const CxMap *map) { + return map->collection.size; +} /** * Creates a value iterator for a map. * - * \note An iterator iterates over all elements successively. Therefore the order + * When the map is storing pointers, those pointers are returned. + * Otherwise, the iterator iterates over pointers to the memory within the map where the + * respective elements are stored. + * + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxIterator cxMapIteratorValues(CxMap const *map) { +cx_attr_nonnull +cx_attr_nodiscard +static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } /** * Creates a key iterator for a map. * - * The elements of the iterator are keys of type CxHashKey. + * The elements of the iterator are keys of type CxHashKey and the pointer returned + * during iterator shall be treated as @c const @c CxHashKey* . * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxIterator cxMapIteratorKeys(CxMap const *map) { +cx_attr_nonnull +cx_attr_nodiscard +static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } /** * Creates an iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry. + * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * during iterator shall be treated as @c const @c CxMapEntry* . * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for @@ -253,8 +318,9 @@ * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxIterator cxMapIterator(CxMap const *map) { +cx_attr_nonnull +cx_attr_nodiscard +static inline CxMapIterator cxMapIterator(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -262,35 +328,45 @@ /** * Creates a mutating iterator over the values of a map. * - * \note An iterator iterates over all elements successively. Therefore the order + * When the map is storing pointers, those pointers are returned. + * Otherwise, the iterator iterates over pointers to the memory within the map where the + * respective elements are stored. + * + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxMutIterator cxMapMutIteratorValues(CxMap *map); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxMapIterator cxMapMutIteratorValues(CxMap *map); /** * Creates a mutating iterator over the keys of a map. * - * The elements of the iterator are keys of type CxHashKey. + * The elements of the iterator are keys of type CxHashKey and the pointer returned + * during iterator shall be treated as @c const @c CxHashKey* . * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxMutIterator cxMapMutIteratorKeys(CxMap *map); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxMapIterator cxMapMutIteratorKeys(CxMap *map); /** * Creates a mutating iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry. + * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * during iterator shall be treated as @c const @c CxMapEntry* . * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for @@ -298,21 +374,14 @@ * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxMutIterator cxMapMutIterator(CxMap *map); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxMapIterator cxMapMutIterator(CxMap *map); #ifdef __cplusplus } // end the extern "C" block here, because we want to start overloading - -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, CxHashKey const &key, @@ -321,16 +390,7 @@ return map->cl->put(map, key, value); } - -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxstring const &key, @@ -339,15 +399,7 @@ return map->cl->put(map, cx_hash_key_cxstr(key), value); } -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxmutstr const &key, @@ -356,378 +408,133 @@ return map->cl->put(map, cx_hash_key_cxstr(key), value); } -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cxMapPut( CxMap *map, - char const *key, + const char *key, void *value ) { return map->cl->put(map, cx_hash_key_str(key), value); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( - CxMap const *map, + const CxMap *map, CxHashKey const &key ) { return map->cl->get(map, key); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( - CxMap const *map, + const CxMap *map, cxstring const &key ) { return map->cl->get(map, cx_hash_key_cxstr(key)); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( - CxMap const *map, + const CxMap *map, cxmutstr const &key ) { return map->cl->get(map, cx_hash_key_cxstr(key)); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cxMapGet( - CxMap const *map, - char const *key + const CxMap *map, + const char *key ) { return map->cl->get(map, cx_hash_key_str(key)); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, CxHashKey const &key ) { - (void) map->cl->remove(map, key, true); -} - -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( - CxMap *map, - cxstring const &key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( - CxMap *map, - cxmutstr const &key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, key, nullptr); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( - CxMap *map, - char const *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( - CxMap *map, - CxHashKey const &key -) { - (void) map->cl->remove(map, key, false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, cxstring const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); } -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, cxmutstr const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( - CxMap *map, - char const *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); } -/** - * Removes a key/value-pair from the map by using the key. - * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cxMapRemoveAndGet( +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxMapRemove( CxMap *map, - CxHashKey key + const char *key ) { - return map->cl->remove(map, key, !map->store_pointer); + 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); } -/** - * Removes a key/value-pair from the map by using the key. - * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cxMapRemoveAndGet( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( CxMap *map, - cxstring key + cxstring key, + void *targetbuf ) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer); + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); } -/** - * Removes a key/value-pair from the map by using the key. - * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cxMapRemoveAndGet( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( CxMap *map, - cxmutstr key + cxmutstr key, + void *targetbuf ) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer); + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); } -/** - * Removes a key/value-pair from the map by using the key. - * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cxMapRemoveAndGet( +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cxMapRemoveAndGet( CxMap *map, - char const *key + const char *key, + void *targetbuf ) { - return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer); + return map->cl->remove(map, cx_hash_key_str(key), targetbuf); } #else // __cplusplus /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put( CxMap *map, CxHashKey key, @@ -737,14 +544,9 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_cxstr( CxMap *map, cxstring key, @@ -754,14 +556,9 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_mustr( CxMap *map, cxmutstr key, @@ -771,17 +568,13 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cx_map_put_str( CxMap *map, - char const *key, + const char *key, void *value ) { return map->cl->put(map, cx_hash_key_str(key), value); @@ -790,75 +583,75 @@ /** * Puts a key/value-pair into the map. * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If this map is storing pointers, the @p value pointer is written + * to the map. Otherwise, the memory is copied from @p value with + * memcpy(). + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero value on memory allocation failure */ #define cxMapPut(map, key, value) _Generic((key), \ CxHashKey: cx_map_put, \ cxstring: cx_map_put_cxstr, \ cxmutstr: cx_map_put_mustr, \ char*: cx_map_put_str, \ - char const*: cx_map_put_str) \ + const char*: cx_map_put_str) \ (map, key, value) /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get( - CxMap const *map, + const CxMap *map, CxHashKey key ) { return map->cl->get(map, key); } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_cxstr( - CxMap const *map, + const CxMap *map, cxstring key ) { return map->cl->get(map, cx_hash_key_cxstr(key)); } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_mustr( - CxMap const *map, + const CxMap *map, cxmutstr key ) { return map->cl->get(map, cx_hash_key_cxstr(key)); } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cx_map_get_str( - CxMap const *map, - char const *key + const CxMap *map, + const char *key ) { return map->cl->get(map, cx_hash_key_str(key)); } @@ -866,268 +659,166 @@ /** * Retrieves a value by using a key. * - * @param map the map - * @param key the key - * @return the value + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @return (@c void*) the value */ #define cxMapGet(map, key) _Generic((key), \ CxHashKey: cx_map_get, \ cxstring: cx_map_get_cxstr, \ cxmutstr: cx_map_get_mustr, \ char*: cx_map_get_str, \ - char const*: cx_map_get_str) \ + const char*: cx_map_get_str) \ (map, key) /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemove() */ -__attribute__((__nonnull__)) -static inline void cx_map_remove( +cx_attr_nonnull +static inline int cx_map_remove( CxMap *map, CxHashKey key ) { - (void) map->cl->remove(map, key, true); + return map->cl->remove(map, key, NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove_cxstr( + CxMap *map, + cxstring key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); } /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemove() */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_cxstr( +cx_attr_nonnull +static inline int cx_map_remove_mustr( CxMap *map, - cxstring key + cxmutstr key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_map_remove_str( + CxMap *map, + const char *key +) { + return map->cl->remove(map, cx_hash_key_str(key), NULL); } /** * Removes a key/value-pair from the map by using the key. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. + * Always invokes the destructors functions, if any, on the removed element. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_str( - CxMap *map, - char const *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @retval zero success + * @retval non-zero the key was not found + * * @see cxMapRemoveAndGet() - * @see cxMapDetach() */ #define cxMapRemove(map, key) _Generic((key), \ CxHashKey: cx_map_remove, \ cxstring: cx_map_remove_cxstr, \ cxmutstr: cx_map_remove_mustr, \ char*: cx_map_remove_str, \ - char const*: cx_map_remove_str) \ + const char*: cx_map_remove_str) \ (map, key) /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_detach( - CxMap *map, - CxHashKey key -) { - (void) map->cl->remove(map, key, false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_cxstr( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get( CxMap *map, - cxstring key + CxHashKey key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_mustr( - CxMap *map, - cxmutstr key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); + return map->cl->remove(map, key, targetbuf); } /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_str( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_cxstr( CxMap *map, - char const *key + cxstring key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); } /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() + * @copydoc cxMapRemoveAndGet() */ -#define cxMapDetach(map, key) _Generic((key), \ - CxHashKey: cx_map_detach, \ - cxstring: cx_map_detach_cxstr, \ - cxmutstr: cx_map_detach_mustr, \ - char*: cx_map_detach_str, \ - char const*: cx_map_detach_str) \ - (map, key) +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_mustr( + CxMap *map, + cxmutstr key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get( +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cx_map_remove_and_get_str( CxMap *map, - CxHashKey key + const char *key, + void *targetbuf ) { - return map->cl->remove(map, key, !map->store_pointer); + return map->cl->remove(map, cx_hash_key_str(key), targetbuf); } /** * Removes a key/value-pair from the map by using the key. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * This function will copy the contents of the removed element + * to the target buffer, which must be guaranteed to be large enough + * to hold the element (the map's element size). + * The destructor functions, if any, will @em not be called. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * If this map is storing pointers, the element is the pointer itself + * and not the object it points to. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_str( - CxMap *map, - char const *key -) { - return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param targetbuf (@c void*) the buffer where the element shall be copied to + * @retval zero success + * @retval non-zero the key was not found * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() + * @see cxMapRemove() */ -#define cxMapRemoveAndGet(map, key) _Generic((key), \ +#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ CxHashKey: cx_map_remove_and_get, \ cxstring: cx_map_remove_and_get_cxstr, \ cxmutstr: cx_map_remove_and_get_mustr, \ char*: cx_map_remove_and_get_str, \ - char const*: cx_map_remove_and_get_str) \ - (map, key) + const char*: cx_map_remove_and_get_str) \ + (map, key, targetbuf) #endif // __cplusplus
--- a/src/ucx/cx/mempool.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/mempool.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file mempool.h - * \brief Interface for memory pool implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file mempool.h + * @brief Interface for memory pool implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_MEMPOOL_H @@ -53,7 +52,7 @@ */ struct cx_mempool_s { /** The provided allocator. */ - CxAllocator const *allocator; + const CxAllocator *allocator; /** * A destructor that shall be automatically registered for newly allocated memory. @@ -77,35 +76,35 @@ typedef struct cx_mempool_s CxMempool; /** + * Deallocates a memory pool and frees the managed memory. + * + * @param pool the memory pool to free + */ +cx_attr_export +void cxMempoolFree(CxMempool *pool); + +/** * Creates an array-based memory pool with a shared destructor function. * * This destructor MUST NOT free the memory. * * @param capacity the initial capacity of the pool - * @param destr the destructor function to use for allocated memory - * @return the created memory pool or \c NULL if allocation failed + * @param destr optional destructor function to use for allocated memory + * @return the created memory pool or @c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMempoolFree, 1) +cx_attr_export CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); /** * Creates a basic array-based memory pool. * - * @param capacity the initial capacity of the pool - * @return the created memory pool or \c NULL if allocation failed + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) -static inline CxMempool *cxBasicMempoolCreate(size_t capacity) { - return cxMempoolCreate(capacity, NULL); -} - -/** - * Destroys a memory pool and frees the managed memory. - * - * @param pool the memory pool to destroy - */ -__attribute__((__nonnull__)) -void cxMempoolDestroy(CxMempool *pool); +#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, NULL) /** * Sets the destructor function for a specific allocated memory object. @@ -116,13 +115,26 @@ * @param memory the object allocated in the pool * @param fnc the destructor function */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export void cxMempoolSetDestructor( void *memory, cx_destructor_func fnc ); /** + * Removes the destructor function for a specific allocated memory object. + * + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +cx_attr_export +void cxMempoolRemoveDestructor(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. @@ -131,11 +143,13 @@ * If that allocation fails, this function will return non-zero. * * @param pool the pool - * @param memory the object allocated in the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) * @param destr the destructor function - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_export int cxMempoolRegister( CxMempool *pool, void *memory,
--- a/src/ucx/cx/printf.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/printf.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file printf.h - * \brief Wrapper for write functions with a printf-like interface. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file printf.h + * @brief Wrapper for write functions with a printf-like interface. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_PRINTF_H @@ -41,52 +40,74 @@ #include "string.h" #include <stdarg.h> +/** + * Attribute for printf-like functions. + * @param fmt_idx index of the format string parameter + * @param arg_idx index of the first formatting argument + */ +#define cx_attr_printf(fmt_idx, arg_idx) \ + __attribute__((__format__(printf, fmt_idx, arg_idx))) + #ifdef __cplusplus extern "C" { #endif + /** - * A \c fprintf like function which writes the output to a stream by + * The maximum string length that fits into stack memory. + */ +cx_attr_export +extern const unsigned cx_printf_sbo_size; + +/** + * A @c fprintf like function which writes the output to a stream by * using a write_func. * * @param stream the stream the data is written to * @param wfc the write function * @param fmt format string * @param ... additional arguments - * @return the total number of bytes written + * @return the total number of bytes written or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4))) +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_printf(3, 4) +cx_attr_cstr_arg(3) +cx_attr_export int cx_fprintf( void *stream, cx_write_func wfc, - char const *fmt, + const char *fmt, ... ); /** - * A \c vfprintf like function which writes the output to a stream by + * A @c vfprintf like function which writes the output to a stream by * using a write_func. * * @param stream the stream the data is written to * @param wfc the write function * @param fmt format string * @param ap argument list - * @return the total number of bytes written + * @return the total number of bytes written or an error code from stdlib printf implementation * @see cx_fprintf() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(3) +cx_attr_export int cx_vfprintf( void *stream, cx_write_func wfc, - char const *fmt, + const char *fmt, va_list ap ); /** - * A \c asprintf like function which allocates space for a string + * A @c asprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * * @param allocator the CxAllocator used for allocating the string * @param fmt format string @@ -94,32 +115,39 @@ * @return the formatted string * @see cx_strfree_a() */ -__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3))) +cx_attr_nonnull_arg(1, 2) +cx_attr_printf(2, 3) +cx_attr_cstr_arg(2) +cx_attr_export cxmutstr cx_asprintf_a( - CxAllocator const *allocator, - char const *fmt, + const CxAllocator *allocator, + const char *fmt, ... ); /** - * A \c asprintf like function which allocates space for a string + * A @c asprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * - * @param fmt format string + * @param fmt (@c char*) format string * @param ... additional arguments - * @return the formatted string + * @return (@c cxmutstr) the formatted string * @see cx_strfree() */ #define cx_asprintf(fmt, ...) \ cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) /** -* A \c vasprintf like function which allocates space for a string +* A @c vasprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * * @param allocator the CxAllocator used for allocating the string * @param fmt format string @@ -127,37 +155,247 @@ * @return the formatted string * @see cx_asprintf_a() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) +cx_attr_export cxmutstr cx_vasprintf_a( - CxAllocator const *allocator, - char const *fmt, + const CxAllocator *allocator, + const char *fmt, va_list ap ); /** -* A \c vasprintf like function which allocates space for a string +* A @c vasprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was in error, in which case the string's pointer + * will be @c NULL. * - * @param fmt format string - * @param ap argument list - * @return the formatted string + * @param fmt (@c char*) format string + * @param ap (@c va_list) argument list + * @return (@c cxmutstr) the formatted string * @see cx_asprintf() */ #define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap) /** - * A \c printf like function which writes the output to a CxBuffer. + * A @c printf like function which writes the output to a CxBuffer. + * + * @param buffer (@c CxBuffer*) a pointer to the buffer the data is written to + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the total number of bytes written or an error code from stdlib printf implementation + * @see cx_fprintf() + * @see cxBufferWrite() + */ +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ + cxBufferWriteFunc, fmt, __VA_ARGS__) + + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * @param buffer a pointer to the buffer the data is written to + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @attention The original buffer MUST have been allocated with the same allocator! + * + * @param alloc the allocator to use + * @param str a pointer to the string buffer + * @param len a pointer to the length of the buffer * @param fmt the format string * @param ... additional arguments - * @return the total number of bytes written - * @see ucx_fprintf() + * @return the length of produced string or an error code from stdlib printf implementation + */ +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( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + ... +); + + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @attention The original buffer MUST have been allocated with the same allocator! + * + * @param alloc the allocator to use + * @param str a pointer to the string buffer + * @param len a pointer to the length of the buffer + * @param fmt the format string + * @param ap argument list + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull +cx_attr_cstr_arg(4) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +cx_attr_export +int cx_vsprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + va_list ap +); + + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the length of produced string or an error code from stdlib printf implementation */ -#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \ - (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) +#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param alloc the allocator to use + * @param buf a pointer to the buffer + * @param len a pointer to the length of the buffer + * @param str a pointer where the location of the result shall be stored + * @param fmt the format string + * @param ... additional arguments + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull_arg(1, 2, 4, 5) +cx_attr_printf(5, 6) +cx_attr_cstr_arg(5) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +cx_attr_access_rw(4) +cx_attr_export +int cx_sprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + ... +); + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param alloc the allocator to use + * @param buf a pointer to the buffer + * @param len a pointer to the length of the buffer + * @param str a pointer where the location of the result shall be stored + * @param fmt the format string + * @param ap argument list + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull +cx_attr_cstr_arg(5) +cx_attr_export +int cx_vsprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +); + #ifdef __cplusplus } // extern "C"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/cx/properties.h Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,667 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * @brief Interface for parsing data from properties files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "common.h" +#include "string.h" +#include "map.h" +#include "buffer.h" + +#include <stdio.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Configures the expected characters for the properties parser. + */ +struct cx_properties_config_s { + /** + * The key/value delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; + + /* + * The character, when appearing at the end of a line, continues that line. + * This is '\' by default. + */ + /** + * Reserved for future use. + */ + char continuation; +}; + +/** + * Typedef for the properties config. + */ +typedef struct cx_properties_config_s CxPropertiesConfig; + +/** + * Default properties configuration. + */ +cx_attr_export +extern const CxPropertiesConfig cx_properties_config_default; + +/** + * Status codes for the properties interface. + */ +enum cx_properties_status { + /** + * Everything is fine. + */ + CX_PROPERTIES_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_PROPERTIES_NO_DATA, + /** + * The input ends unexpectedly. + * + * This either happens when the last line does not terminate with a line + * break, or when the input ends with a parsed key but no value. + */ + CX_PROPERTIES_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_PROPERTIES_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_PROPERTIES_OK, + /** + * Input buffer is @c NULL. + */ + CX_PROPERTIES_NULL_INPUT, + /** + * The line contains a delimiter, but no key. + */ + CX_PROPERTIES_INVALID_EMPTY_KEY, + /** + * The line contains data, but no delimiter. + */ + CX_PROPERTIES_INVALID_MISSING_DELIMITER, + /** + * More internal buffer was needed, but could not be allocated. + */ + CX_PROPERTIES_BUFFER_ALLOC_FAILED, + /** + * Initializing the properties source failed. + * + * @see cx_properties_read_init_func + */ + CX_PROPERTIES_READ_INIT_FAILED, + /** + * Reading from a properties source failed. + * + * @see cx_properties_read_func + */ + CX_PROPERTIES_READ_FAILED, + /** + * Sinking a k/v-pair failed. + * + * @see cx_properties_sink_func + */ + CX_PROPERTIES_SINK_FAILED, +}; + +/** + * Typedef for the properties status enum. + */ +typedef enum cx_properties_status CxPropertiesStatus; + +/** + * Interface for working with properties data. + */ +struct cx_properties_s { + /** + * The configuration. + */ + CxPropertiesConfig config; + + /** + * The text input buffer. + */ + CxBuffer input; + + /** + * Internal buffer. + */ + CxBuffer buffer; +}; + +/** + * Typedef for the properties interface. + */ +typedef struct cx_properties_s CxProperties; + + +/** + * Typedef for a properties sink. + */ +typedef struct cx_properties_sink_s CxPropertiesSink; + +/** + * A function that consumes a k/v-pair in a sink. + * + * The sink could be e.g. a map and the sink function would be calling + * a map function to store the k/v-pair. + * + * @param prop the properties interface that wants to sink a k/v-pair + * @param sink the sink + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero sinking the k/v-pair failed + */ +cx_attr_nonnull +typedef int(*cx_properties_sink_func)( + CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +); + +/** + * Defines a sink for k/v-pairs. + */ +struct cx_properties_sink_s { + /** + * The sink object. + */ + void *sink; + /** + * Optional custom data. + */ + void *data; + /** + * A function for consuming k/v-pairs into the sink. + */ + cx_properties_sink_func sink_func; +}; + + +/** + * Typedef for a properties source. + */ +typedef struct cx_properties_source_s CxPropertiesSource; + +/** + * A function that reads data from a source. + * + * When the source is depleted, implementations SHALL provide an empty + * string in the @p target and return zero. + * A non-zero return value is only permitted in case of an error. + * + * The meaning of the optional parameters is implementation-dependent. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @param target a string buffer where the read data shall be stored + * @retval zero success + * @retval non-zero reading the data failed + */ +cx_attr_nonnull +typedef int(*cx_properties_read_func)( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +); + +/** + * A function that may initialize additional memory for the source. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @retval zero initialization was successful + * @retval non-zero otherwise + */ +cx_attr_nonnull +typedef int(*cx_properties_read_init_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * A function that cleans memory initialized by the read_init_func. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + */ +cx_attr_nonnull +typedef void(*cx_properties_read_clean_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * Defines a properties source. + */ +struct cx_properties_source_s { + /** + * The source object. + * + * For example a file stream or a string. + */ + void *src; + /** + * Optional additional data pointer. + */ + void *data_ptr; + /** + * Optional size information. + */ + size_t data_size; + /** + * A function that reads data from the source. + */ + cx_properties_read_func read_func; + /** + * Optional function that may prepare the source for reading data. + */ + cx_properties_read_init_func read_init_func; + /** + * Optional function that cleans additional memory allocated by the + * read_init_func. + */ + cx_properties_read_clean_func read_clean_func; +}; + +/** + * Initialize a properties interface. + * + * @param prop the properties interface + * @param config the properties configuration + * @see cxPropertiesInitDefault() + */ +cx_attr_nonnull +cx_attr_export +void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); + +/** + * Destroys the properties interface. + * + * @note Even when you are certain that you did not use the interface in a + * way that caused a memory allocation, you should call this function anyway. + * Future versions of the library might add features that need additional memory + * and you really don't want to search the entire code where you might need + * add call to this function. + * + * @param prop the properties interface + */ +cx_attr_nonnull +cx_attr_export +void cxPropertiesDestroy(CxProperties *prop); + +/** + * Destroys and re-initializes the properties interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param prop the properties interface + */ +cx_attr_nonnull +static inline void cxPropertiesReset(CxProperties *prop) { + CxPropertiesConfig config = prop->config; + cxPropertiesDestroy(prop); + cxPropertiesInit(prop, config); +} + +/** + * Initialize a properties parser with the default configuration. + * + * @param prop (@c CxProperties*) the properties interface + * @see cxPropertiesInit() + */ +#define cxPropertiesInitDefault(prop) \ + cxPropertiesInit(prop, cx_properties_config_default) + +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @remark The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param buf a pointer to the data + * @param len the length of the data + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +); + +#ifdef __cplusplus +} // extern "C" +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxPropertiesFill( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @attention The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFilln() + */ +#define cxPropertiesFill(prop, str) _Generic((str), \ + cxstring: cx_properties_fill_cxstr, \ + cxmutstr: cx_properties_fill_mutstr, \ + char*: cx_properties_fill_str, \ + const char*: cx_properties_fill_str) \ + (prop, str) + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_cxstr( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_mutstr( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_properties_fill_str( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} +#endif + +/** + * Specifies stack memory that shall be used as internal buffer. + * + * @param prop the properties interface + * @param buf a pointer to stack memory + * @param capacity the capacity of the stack memory + */ +cx_attr_nonnull +cx_attr_export +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +); + +/** + * Retrieves the next key/value-pair. + * + * This function returns zero as long as there are key/value-pairs found. + * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned. + * + * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is + * returned, and you can add more data with #cxPropertiesFill(). + * + * @remark The incomplete line will be stored in an internal buffer, which is + * allocated on the heap, by default. If you want to avoid allocations, + * you can specify sufficient space with cxPropertiesUseStack() after + * initialization with cxPropertiesInit(). + * + * @attention The returned strings will point into a buffer that might not be + * available later. It is strongly recommended to copy the strings for further + * use. + * + * @param prop the properties interface + * @param key a pointer to the cxstring that shall contain the property name + * @param value a pointer to the cxstring that shall contain the property value + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer + * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete + * (fill more data and try again) + * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +); + +/** + * Creates a properties sink for an UCX map. + * + * The values stored in the map will be pointers to freshly allocated, + * zero-terminated C strings (@c char*), which means the @p map should have been + * created with #CX_STORE_POINTERS. + * + * The default stdlib allocator will be used, unless you specify a custom + * allocator in the optional @c data field of the returned sink. + * + * @param map the map that shall consume the k/v-pairs. + * @return the sink + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +CxPropertiesSink cxPropertiesMapSink(CxMap *map); + +/** + * Creates a properties source based on an UCX string. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nodiscard +cx_attr_export +CxPropertiesSource cxPropertiesStringSource(cxstring str); + +/** + * Creates a properties source based on C string with the specified length. + * + * @param str the string + * @param len the length + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1, 2) +cx_attr_export +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); + +/** + * Creates a properties source based on a C string. + * + * The length will be determined with strlen(), so the string MUST be + * zero-terminated. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_export +CxPropertiesSource cxPropertiesCstrSource(const char *str); + +/** + * Creates a properties source based on an FILE. + * + * @param file the file + * @param chunk_size how many bytes may be read in one operation + * + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1) +cx_attr_export +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); + + +/** + * Loads properties data from a source and transfers it to a sink. + * + * This function tries to read as much data from the source as possible. + * When the source was completely consumed and at least on k/v-pair was found, + * the return value will be #CX_PROPERTIES_NO_ERROR. + * When the source was consumed but no k/v-pairs were found, the return value + * will be #CX_PROPERTIES_NO_DATA. + * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA + * is returned. In that case you should call this function again with the same + * sink and either an updated source or the same source if the source is able to + * yield the missing data. + * + * The other result codes apply, according to their description. + * + * @param prop the properties interface + * @param sink the sink + * @param source the source + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed + * @retval CX_PROPERTIES_READ_FAILED reading from the source failed + * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed + * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs + * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +cx_attr_export +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_PROPERTIES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/cx/streams.h Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,137 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file streams.h + * + * @brief Utility functions for data streams. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_STREAMS_H +#define UCX_STREAMS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can + * set this to zero to let the implementation decide + * @param n the maximum number of bytes that shall be copied. + * If this is larger than @p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_access_w(5) +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 +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is + * @c NULL you can set this to zero to let the implementation decide + * @return total number of bytes copied + */ +#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +cx_attr_nonnull +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_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. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_STREAMS_H
--- a/src/ucx/cx/string.h Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/cx/string.h Sun Mar 02 18:10:52 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file string.h - * \brief Strings that know their length. - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License + * @file string.h + * @brief Strings that know their length. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_STRING_H @@ -41,13 +40,18 @@ #include "allocator.h" /** + * The maximum length of the "needle" in cx_strstr() that can use SBO. + */ +cx_attr_export +extern const unsigned cx_strstr_sbo_size; + +/** * The UCX string structure. */ struct cx_mutstr_s { /** * A pointer to the string. - * \note The string is not necessarily \c NULL terminated. - * Always use the length. + * @note The string is not necessarily @c NULL terminated. */ char *ptr; /** The length of the string */ @@ -65,10 +69,9 @@ struct cx_string_s { /** * A pointer to the immutable string. - * \note The string is not necessarily \c NULL terminated. - * Always use the length. + * @note The string is not necessarily @c NULL terminated. */ - char const *ptr; + const char *ptr; /** The length of the string */ size_t length; }; @@ -93,7 +96,7 @@ /** * Optional array of more delimiters. */ - cxstring const *delim_more; + const cxstring *delim_more; /** * Length of the array containing more delimiters. */ @@ -144,11 +147,11 @@ /** * A literal initializer for an UCX string structure. * - * The argument MUST be a string (const char*) \em literal. + * The argument MUST be a string (const char*) @em literal. * * @param literal the string literal */ -#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1} +#define CX_STR(literal) ((cxstring){literal, sizeof(literal) - 1}) #endif @@ -156,9 +159,9 @@ /** * Wraps a mutable string that must be zero-terminated. * - * The length is implicitly inferred by using a call to \c strlen(). + * The length is implicitly inferred by using a call to @c strlen(). * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a constant string, use cx_str(). @@ -168,26 +171,31 @@ * * @see cx_mutstrn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_export cxmutstr cx_mutstr(char *cstring); /** * Wraps a string that does not need to be zero-terminated. * - * The argument may be \c NULL if the length is zero. + * The argument may be @c NULL if the length is zero. * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a constant string, use cx_strn(). * - * @param cstring the string to wrap (or \c NULL, only if the length is zero) + * @param cstring the string to wrap (or @c NULL, only if the length is zero) * @param length the length of the string * @return the wrapped string * * @see cx_mutstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_rw(1, 2) +cx_attr_export cxmutstr cx_mutstrn( char *cstring, size_t length @@ -196,9 +204,9 @@ /** * Wraps a string that must be zero-terminated. * - * The length is implicitly inferred by using a call to \c strlen(). + * The length is implicitly inferred by using a call to @c strlen(). * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a non-constant string, use cx_mutstr(). @@ -208,88 +216,137 @@ * * @see cx_strn() */ -__attribute__((__warn_unused_result__, __nonnull__)) -cxstring cx_str(char const *cstring); +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_export +cxstring cx_str(const char *cstring); /** * Wraps a string that does not need to be zero-terminated. * - * The argument may be \c NULL if the length is zero. + * The argument may be @c NULL if the length is zero. * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a non-constant string, use cx_mutstrn(). * - * @param cstring the string to wrap (or \c NULL, only if the length is zero) + * @param cstring the string to wrap (or @c NULL, only if the length is zero) * @param length the length of the string * @return the wrapped string * * @see cx_str() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) +cx_attr_export cxstring cx_strn( - char const *cstring, + const char *cstring, size_t length ); +#ifdef __cplusplus +} // extern "C" +cx_attr_nodiscard +static inline cxstring cx_strcast(cxmutstr str) { + return cx_strn(str.ptr, str.length); +} +cx_attr_nodiscard +static inline cxstring cx_strcast(cxstring str) { + return str; +} +extern "C" { +#else +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_m(cxmutstr str) { + return (cxstring) {str.ptr, str.length}; +} +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_c(cxstring str) { + return str; +} + /** * Casts a mutable string to an immutable string. * -* \note This is not seriously a cast. Instead you get a copy +* Does nothing for already immutable strings. +* +* @note This is not seriously a cast. Instead, you get a copy * of the struct with the desired pointer type. Both structs still * point to the same location, though! * -* @param str the mutable string to cast -* @return an immutable copy of the string pointer +* @param str (@c cxstring or @c cxmutstr) the string to cast +* @return (@c cxstring) an immutable copy of the string pointer */ -__attribute__((__warn_unused_result__)) -cxstring cx_strcast(cxmutstr str); +#define cx_strcast(str) _Generic((str), \ + cxmutstr: cx_strcast_m, \ + cxstring: cx_strcast_c) \ + (str) +#endif /** - * Passes the pointer in this string to \c free(). - * - * The pointer in the struct is set to \c NULL and the length is set to zero. + * Passes the pointer in this string to @c free(). * - * \note There is no implementation for cxstring, because it is unlikely that - * you ever have a \c char \c const* you are really supposed to free. If you - * encounter such situation, you should double-check your code. + * 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. * * @param str the string to free */ -__attribute__((__nonnull__)) +cx_attr_export void cx_strfree(cxmutstr *str); /** * Passes the pointer in this string to the allocators free function. * - * The pointer in the struct is set to \c NULL and the length is set to zero. + * The pointer in the struct is set to @c NULL and the length is set to zero + * which means that this function protects you against double-free. * - * \note There is no implementation for cxstring, because it is unlikely that - * you ever have a \c char \c const* you are really supposed to free. If you - * encounter such situation, you should double-check your code. + * @note There is no implementation for cxstring, because it is unlikely that + * you ever have a <code>const char*</code> you are really supposed to free. + * If you encounter such situation, you should double-check your code. * * @param alloc the allocator * @param str the string to free */ -__attribute__((__nonnull__)) +cx_attr_nonnull_arg(1) +cx_attr_export void cx_strfree_a( - CxAllocator const *alloc, + const CxAllocator *alloc, cxmutstr *str ); /** * Returns the accumulated length of all specified strings. + * + * If this sum overflows, errno is set to EOVERFLOW. * - * \attention if the count argument is larger than the number of the + * @attention if the count argument is larger than the number of the * specified strings, the behavior is undefined. * * @param count the total number of specified strings * @param ... all strings * @return the accumulated length of all strings */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export size_t cx_strlen( size_t count, ... @@ -299,23 +356,29 @@ * Concatenates strings. * * The resulting string will be allocated by the specified allocator. - * So developers \em must pass the return value to cx_strfree_a() eventually. + * So developers @em must pass the return value to cx_strfree_a() eventually. * - * If \p str already contains a string, the memory will be reallocated and + * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * \note It is guaranteed that there is only one allocation. + * If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * * @param alloc the allocator to use * @param str the string the other strings shall be concatenated to * @param count the number of the other following strings to concatenate - * @param ... all other strings + * @param ... all other UCX strings * @return the concatenated string */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export cxmutstr cx_strcat_ma( - CxAllocator const *alloc, + const CxAllocator *alloc, cxmutstr str, size_t count, ... @@ -325,15 +388,19 @@ * Concatenates strings and returns a new string. * * The resulting string will be allocated by the specified allocator. - * So developers \em must pass the return value to cx_strfree_a() eventually. + * So developers @em must pass the return value to cx_strfree_a() eventually. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param alloc the allocator to use - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param alloc (@c CxAllocator*) the allocator to use + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_a(alloc, count, ...) \ cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) @@ -341,15 +408,19 @@ /** * Concatenates strings and returns a new string. * - * The resulting string will be allocated by standard \c malloc(). - * So developers \em must pass the return value to cx_strfree() eventually. + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat(count, ...) \ cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) @@ -357,19 +428,23 @@ /** * Concatenates strings. * - * The resulting string will be allocated by standard \c malloc(). - * So developers \em must pass the return value to cx_strfree() eventually. + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. * - * If \p str already contains a string, the memory will be reallocated and + * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param str the string the other strings shall be concatenated to - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param str (@c cxmutstr) the string the other strings shall be concatenated to + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_m(str, count, ...) \ cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) @@ -377,19 +452,20 @@ /** * Returns a substring starting at the specified location. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubsl() * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strsubs( cxstring string, size_t start @@ -398,23 +474,24 @@ /** * Returns a substring starting at the specified location. * - * The returned string will be limited to \p length bytes or the number - * of bytes available in \p string, whichever is smaller. + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring * @param length the maximum length of the returned string - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubs() * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strsubsl( cxstring string, size_t start, @@ -424,19 +501,20 @@ /** * Returns a substring starting at the specified location. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubsl_m() * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strsubs_m( cxmutstr string, size_t start @@ -445,23 +523,24 @@ /** * Returns a substring starting at the specified location. * - * The returned string will be limited to \p length bytes or the number - * of bytes available in \p string, whichever is smaller. + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring * @param length the maximum length of the returned string - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubs_m() * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strsubsl_m( cxmutstr string, size_t start, @@ -476,11 +555,12 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the first location of \p chr + * @return a substring starting at the first location of @p chr * * @see cx_strchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strchr( cxstring string, int chr @@ -494,11 +574,12 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the first location of \p chr + * @return a substring starting at the first location of @p chr * * @see cx_strchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strchr_m( cxmutstr string, int chr @@ -512,11 +593,12 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the last location of \p chr + * @return a substring starting at the last location of @p chr * * @see cx_strrchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strrchr( cxstring string, int chr @@ -530,11 +612,12 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the last location of \p chr + * @return a substring starting at the last location of @p chr * * @see cx_strrchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strrchr_m( cxmutstr string, int chr @@ -544,19 +627,20 @@ * Returns a substring starting at the location of the first occurrence of the * specified string. * - * If \p haystack does not contain \p needle, an empty string is returned. + * If @p haystack does not contain @p needle, an empty string is returned. * - * If \p needle is an empty string, the complete \p haystack is + * If @p needle is an empty string, the complete @p haystack is * returned. * * @param haystack the string to be scanned * @param needle string containing the sequence of characters to match * @return a substring starting at the first occurrence of - * \p needle, or an empty string, if the sequence is not + * @p needle, or an empty string, if the sequence is not * contained * @see cx_strstr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strstr( cxstring haystack, cxstring needle @@ -566,19 +650,20 @@ * Returns a substring starting at the location of the first occurrence of the * specified string. * - * If \p haystack does not contain \p needle, an empty string is returned. + * If @p haystack does not contain @p needle, an empty string is returned. * - * If \p needle is an empty string, the complete \p haystack is + * If @p needle is an empty string, the complete @p haystack is * returned. * * @param haystack the string to be scanned * @param needle string containing the sequence of characters to match * @return a substring starting at the first occurrence of - * \p needle, or an empty string, if the sequence is not + * @p needle, or an empty string, if the sequence is not * contained * @see cx_strstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strstr_m( cxmutstr haystack, cxstring needle @@ -587,16 +672,19 @@ /** * Splits a given string using a delimiter string. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least \p limit length + * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) +cx_attr_export size_t cx_strsplit( cxstring string, cxstring delim, @@ -607,13 +695,13 @@ /** * Splits a given string using a delimiter string. * - * The array pointed to by \p output will be allocated by \p allocator. + * The array pointed to by @p output will be allocated by @p allocator. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * - * \attention If allocation fails, the \c NULL pointer will be written to - * \p output and the number returned will be zero. + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. * * @param allocator the allocator to use for allocating the resulting array * @param string the string to split @@ -623,9 +711,12 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) +cx_attr_export size_t cx_strsplit_a( - CxAllocator const *allocator, + const CxAllocator *allocator, cxstring string, cxstring delim, size_t limit, @@ -636,16 +727,19 @@ /** * Splits a given string using a delimiter string. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least \p limit length + * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) +cx_attr_export size_t cx_strsplit_m( cxmutstr string, cxstring delim, @@ -656,13 +750,13 @@ /** * Splits a given string using a delimiter string. * - * The array pointed to by \p output will be allocated by \p allocator. + * The array pointed to by @p output will be allocated by @p allocator. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * - * \attention If allocation fails, the \c NULL pointer will be written to - * \p output and the number returned will be zero. + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. * * @param allocator the allocator to use for allocating the resulting array * @param string the string to split @@ -672,9 +766,12 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) +cx_attr_export size_t cx_strsplit_ma( - CxAllocator const *allocator, + const CxAllocator *allocator, cxmutstr string, cxstring delim, size_t limit, @@ -686,10 +783,11 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export int cx_strcmp( cxstring s1, cxstring s2 @@ -700,10 +798,11 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal ignoring case + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export int cx_strcasecmp( cxstring s1, cxstring s2 @@ -716,13 +815,15 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export int cx_strcmp_p( - void const *s1, - void const *s2 + const void *s1, + const void *s2 ); /** @@ -732,99 +833,93 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal ignoring case + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export int cx_strcasecmp_p( - void const *s1, - void const *s2 + const void *s1, + const void *s2 ); /** * Creates a duplicate of the specified string. * - * The new string will contain a copy allocated by \p allocator. + * The new string will contain a copy allocated by @p allocator. * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * * @param allocator the allocator to use * @param string the string to duplicate * @return a duplicate of the string * @see cx_strdup() */ -__attribute__((__warn_unused_result__, __nonnull__)) -cxmutstr cx_strdup_a( - CxAllocator const *allocator, +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export +cxmutstr cx_strdup_a_( + const CxAllocator *allocator, cxstring string ); /** * Creates a duplicate of the specified string. * - * The new string will contain a copy allocated by standard - * \c malloc(). So developers \em must pass the return value to cx_strfree(). + * The new string will contain a copy allocated by @p allocator. + * + * @note The returned string is guaranteed to be zero-terminated. * - * \note The returned string is guaranteed to be zero-terminated. - * + * @param allocator (@c CxAllocator*) the allocator to use * @param string the string to duplicate - * @return a duplicate of the string - * @see cx_strdup_a() + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup() + * @see cx_strfree_a() */ -#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) - - -/** - * Creates a duplicate of the specified string. - * - * The new string will contain a copy allocated by \p allocator. - * - * \note The returned string is guaranteed to be zero-terminated. - * - * @param allocator the allocator to use - * @param string the string to duplicate - * @return a duplicate of the string - * @see cx_strdup_m() - */ -#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string)) +#define cx_strdup_a(allocator, string) \ + cx_strdup_a_((allocator), cx_strcast(string)) /** * Creates a duplicate of the specified string. * * The new string will contain a copy allocated by standard - * \c malloc(). So developers \em must pass the return value to cx_strfree(). + * @c malloc(). So developers @em must pass the return value to cx_strfree(). * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * * @param string the string to duplicate - * @return a duplicate of the string - * @see cx_strdup_ma() + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup_a() + * @see cx_strfree() */ -#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string)) +#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) /** * Omits leading and trailing spaces. * - * \note the returned string references the same memory, thus you - * must \em not free the returned memory. + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. * * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxstring cx_strtrim(cxstring string); /** * Omits leading and trailing spaces. * - * \note the returned string references the same memory, thus you - * must \em not free the returned memory. + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. * * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export cxmutstr cx_strtrim_m(cxmutstr string); /** @@ -832,10 +927,11 @@ * * @param string the string to check * @param prefix the prefix the string should have - * @return \c true, if and only if the string has the specified prefix, - * \c false otherwise + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export bool cx_strprefix( cxstring string, cxstring prefix @@ -846,10 +942,11 @@ * * @param string the string to check * @param suffix the suffix the string should have - * @return \c true, if and only if the string has the specified suffix, - * \c false otherwise + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export bool cx_strsuffix( cxstring string, cxstring suffix @@ -860,10 +957,11 @@ * * @param string the string to check * @param prefix the prefix the string should have - * @return \c true, if and only if the string has the specified prefix, - * \c false otherwise + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export bool cx_strcaseprefix( cxstring string, cxstring prefix @@ -874,42 +972,22 @@ * * @param string the string to check * @param suffix the suffix the string should have - * @return \c true, if and only if the string has the specified suffix, - * \c false otherwise + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_export bool cx_strcasesuffix( cxstring string, cxstring suffix ); /** - * Converts the string to lower case. - * - * The change is made in-place. If you want a copy, use cx_strdup(), first. - * - * @param string the string to modify - * @see cx_strdup() - */ -void cx_strlower(cxmutstr string); - -/** - * Converts the string to upper case. + * Replaces a string with another string. * - * The change is made in-place. If you want a copy, use cx_strdup(), first. + * Replaces at most @p replmax occurrences. * - * @param string the string to modify - * @see cx_strdup() - */ -void cx_strupper(cxmutstr string); - -/** - * Replaces a pattern in a string with another string. - * - * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. - * - * The returned string will be allocated by \p allocator and is guaranteed + * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, @@ -917,80 +995,76 @@ * * @param allocator the allocator to use * @param str the string where replacements should be applied - * @param pattern the pattern to search for + * @param search the string to search for * @param replacement the replacement string * @param replmax maximum number of replacements * @return the resulting string after applying the replacements */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export cxmutstr cx_strreplacen_a( - CxAllocator const *allocator, + const CxAllocator *allocator, cxstring str, - cxstring pattern, + cxstring search, cxstring replacement, size_t replmax ); /** - * Replaces a pattern in a string with another string. + * Replaces a string with another string. * - * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. + * Replaces at most @p replmax occurrences. * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @param replmax maximum number of replacements - * @return the resulting string after applying the replacements + * @param str (@c cxstring) the string where replacements should be applied + * @param search (@c cxstring) the string to search for + * @param replacement (@c cxstring) the replacement string + * @param replmax (@c size_t) maximum number of replacements + * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplacen(str, pattern, replacement, replmax) \ -cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax) +#define cx_strreplacen(str, search, replacement, replmax) \ +cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) /** - * Replaces a pattern in a string with another string. + * Replaces a string with another string. * - * The pattern is taken literally and is no regular expression. - * - * The returned string will be allocated by \p allocator and is guaranteed + * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param allocator the allocator to use - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @return the resulting string after applying the replacements + * @param allocator (@c CxAllocator*) the allocator to use + * @param str (@c cxstring) the string where replacements should be applied + * @param search (@c cxstring) the string to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplace_a(allocator, str, pattern, replacement) \ -cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX) +#define cx_strreplace_a(allocator, str, search, replacement) \ +cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) /** - * Replaces a pattern in a string with another string. + * Replaces a string with another string. * - * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. - * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @return the resulting string after applying the replacements + * @param str (@c cxstring) the string where replacements should be applied + * @param search (@c cxstring) the string to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplace(str, pattern, replacement) \ -cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX) +#define cx_strreplace(str, search, replacement) \ +cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) /** * Creates a string tokenization context. @@ -1000,27 +1074,24 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) -CxStrtokCtx cx_strtok( +cx_attr_nodiscard +cx_attr_export +CxStrtokCtx cx_strtok_( cxstring str, cxstring delim, size_t limit ); /** -* Creates a string tokenization context for a mutable string. -* -* @param str the string to tokenize -* @param delim the delimiter (must not be empty) -* @param limit the maximum number of tokens that shall be returned -* @return a new string tokenization context -*/ -__attribute__((__warn_unused_result__)) -CxStrtokCtx cx_strtok_m( - cxmutstr str, - cxstring delim, - size_t limit -); + * Creates a string tokenization context. + * + * @param str the string to tokenize + * @param delim the delimiter string (must not be empty) + * @param limit (@c size_t) the maximum number of tokens that shall be returned + * @return (@c CxStrtokCtx) a new string tokenization context + */ +#define cx_strtok(str, delim, limit) \ + cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit)) /** * Returns the next token. @@ -1032,7 +1103,10 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) +cx_attr_export bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -1042,6 +1116,8 @@ * Returns the next token of a mutable string. * * The token will point to the source string. + * + * @attention * If the context was not initialized over a mutable string, modifying * the data of the returned token is undefined behavior. * @@ -1050,7 +1126,10 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) +cx_attr_export bool cx_strtok_next_m( CxStrtokCtx *ctx, cxmutstr *token @@ -1063,13 +1142,985 @@ * @param delim array of more delimiters * @param count number of elements in the array */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(2, 3) +cx_attr_export void cx_strtok_delim( CxStrtokCtx *ctx, - cxstring const *delim, + const cxstring *delim, size_t count ); +/* ------------------------------------------------------------------------- * + * string to number conversion functions * + * ------------------------------------------------------------------------- */ + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); + +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtos(str, output, base) cx_strtos_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi(str, output, base) cx_strtoi_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtol(str, output, base) cx_strtol_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoll(str, output, base) cx_strtoll_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi8(str, output, base) cx_strtoi8_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi16(str, output, base) cx_strtoi16_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi32(str, output, base) cx_strtoi32_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi64(str, output, base) cx_strtoi64_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoz(str, output, base) cx_strtoz_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtous(str, output, base) cx_strtous_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou(str, output, base) cx_strtou_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoul(str, output, base) cx_strtoul_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoull(str, output, base) cx_strtoull_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou8(str, output, base) cx_strtou8_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou16(str, output, base) cx_strtou16_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou32(str, output, base) cx_strtou32_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc_(cx_strcast(str), output, decsep, groupsep) + +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc_(cx_strcast(str), output, decsep, groupsep) + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",") + +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod(str, output) cx_strtod_lc_(cx_strcast(str), output, '.', ",") #ifdef __cplusplus } // extern "C"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/cx/test.h Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,338 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + * <code> + * CX_TEST(function_name); + * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional + * </code> + * + * **** IN SOURCE FILE: **** + * <code> + * CX_TEST_SUBROUTINE(subroutine_name, paramlist) { + * // tests with CX_TEST_ASSERT() + * } + * + * CX_TEST(function_name) { + * // memory allocation and other stuff here + * #CX_TEST_DO { + * // tests with CX_TEST_ASSERT() and/or + * // calls with CX_TEST_CALL_SUBROUTINE() here + * } + * // cleanup of memory here + * } + * </code> + * + * @attention Do not call own functions within a test, that use + * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_TEST_H +#define UCX_TEST_H + +#include "common.h" + +#include <stdio.h> +#include <string.h> +#include <setjmp.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ +/** + * Alias for the <code>__func__</code> preprocessor macro. + * Some compilers use <code>__func__</code> and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * <code>__func__</code>. + */ +#define __FUNCTION__ __func__ +#endif + +#if !defined(__clang__) && __GNUC__ > 3 +#pragma GCC diagnostic ignored "-Wclobbered" +#endif + +/** Type for the CxTestSuite. */ +typedef struct CxTestSuite CxTestSuite; + +/** Pointer to a test function. */ +typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func); + +/** Type for the internal list of test cases. */ +typedef struct CxTestSet CxTestSet; + +/** Structure for the internal list of test cases. */ +struct CxTestSet { + + /** Test case. */ + CxTest test; + + /** Pointer to the next list element. */ + CxTestSet *next; +}; + +/** + * A test suite containing multiple test cases. + */ +struct CxTestSuite { + + /** The number of successful tests after the suite has been run. */ + unsigned int success; + + /** The number of failed tests after the suite has been run. */ + unsigned int failure; + + /** The optional name of this test suite. */ + const char *name; + + /** + * Internal list of test cases. + * Use cx_test_register() to add tests to this list. + */ + CxTestSet *tests; +}; + +/** + * Creates a new test suite. + * @param name optional name of the suite + * @return a new test suite + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_malloc +static inline CxTestSuite* cx_test_suite_new(const char *name) { + CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); + if (suite != NULL) { + suite->name = name; + suite->success = 0; + suite->failure = 0; + suite->tests = NULL; + } + + return suite; +} + +/** + * Deallocates a test suite. + * + * @param suite the test suite to free + */ +static inline void cx_test_suite_free(CxTestSuite* suite) { + if (suite == NULL) return; + CxTestSet *l = suite->tests; + while (l != NULL) { + CxTestSet *e = l; + l = l->next; + free(e); + } + free(suite); +} + +/** + * Registers a test function with the specified test suite. + * + * @param suite the suite, the test function shall be added to + * @param test the test function to register + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +static inline int cx_test_register(CxTestSuite* suite, CxTest test) { + CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); + if (t) { + t->test = test; + t->next = NULL; + if (suite->tests == NULL) { + suite->tests = t; + } else { + CxTestSet *last = suite->tests; + while (last->next) { + last = last->next; + } + last->next = t; + } + return 0; + } else { + return 1; + } +} + +/** + * Runs a test suite and writes the test log to the specified stream. + * @param suite the test suite to run + * @param out_target the target buffer or file to write the output to + * @param out_writer the write function writing to @p out_target + */ +cx_attr_nonnull +static inline void cx_test_run(CxTestSuite *suite, + void *out_target, cx_write_func out_writer) { + if (suite->name == NULL) { + out_writer("*** Test Suite ***\n", 1, 19, out_target); + } else { + out_writer("*** Test Suite : ", 1, 17, out_target); + out_writer(suite->name, 1, strlen(suite->name), out_target); + out_writer(" ***\n", 1, 5, out_target); + } + suite->success = 0; + suite->failure = 0; + for (CxTestSet *elem = suite->tests; elem; elem = elem->next) { + elem->test(suite, out_target, out_writer); + } + out_writer("\nAll test completed.\n", 1, 21, out_target); + char total[80]; + int len = snprintf( + total, 80, + " Total: %u\n Success: %u\n Failure: %u\n\n", + suite->success + suite->failure, suite->success, suite->failure + ); + out_writer(total, 1, len, out_target); +} + +/** + * Runs a test suite and writes the test log to the specified FILE stream. + * @param suite (@c CxTestSuite*) the test suite to run + * @param file (@c FILE*) the target file to write the output to + */ +#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite) + +/** + * Runs a test suite and writes the test log to stdout. + * @param suite (@c CxTestSuite*) the test suite to run + */ +#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout) + +/** + * Macro for a #CxTest function header. + * + * Use this macro to declare and/or define a #CxTest function. + * + * @param name the name of the test function + */ +#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_) + +/** + * Defines the scope of a test. + * + * @code + * CX_TEST(my_test_name) { + * // setup code + * CX_TEST_DO { + * // your tests go here + * } + * // tear down code + * } + * @endcode + * + * @attention Any CX_TEST_ASSERT() calls must be performed in scope of + * #CX_TEST_DO. + */ +#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\ + _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ + _writefnc_("... ", 1, 4, _output_);\ + jmp_buf _env_;\ + for (unsigned int _cx_test_loop_ = 0 ;\ + _cx_test_loop_ == 0 && !setjmp(_env_);\ + _writefnc_("success.\n", 1, 9, _output_),\ + _suite_->success++, _cx_test_loop_++) + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition (@c bool) the condition to check + * @param message (@c char*) the message that shall be printed out on failure + */ +#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \ + const char *_assert_msg_ = message; \ + _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \ + _writefnc_(".\n", 1, 2, _output_); \ + _suite_->failure++; \ + longjmp(_env_, 1);\ + } (void) 0 + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition (@c bool) the condition to check + */ +#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed") + +/** + * Macro for a test subroutine function header. + * + * Use this to declare and/or define a subroutine that can be called by using + * CX_TEST_CALL_SUBROUTINE(). + * + * @param name the name of the subroutine + * @param ... the parameter list + * + * @see CX_TEST_CALL_SUBROUTINE() + */ +#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\ + void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__) + +/** + * Macro for calling a test subroutine. + * + * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this + * macro. + * + * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see CX_TEST_SUBROUTINE() + */ +#define CX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/cx/tree.h Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,1428 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file tree.h + * @brief Interface for tree implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_TREE_H +#define UCX_TREE_H + +#include "common.h" + +#include "collection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A depth-first tree iterator. + * + * This iterator is not position-aware in a strict sense, as it does not assume + * a particular order of elements in the tree. However, the iterator keeps track + * of the number of nodes it has passed in a counter variable. + * Each node, regardless of the number of passes, is counted only once. + * + * @note Objects that are pointed to by an iterator are mutable through that + * iterator. However, if the + * underlying data structure is mutated by other means than this iterator (e.g. + * elements added or removed), the iterator becomes invalid (regardless of what + * cxIteratorValid() returns). + * + * @see CxIterator + */ +typedef struct cx_tree_iterator_s { + /** + * Base members. + */ + CX_ITERATOR_BASE; + /** + * Indicates whether the subtree below the current node shall be skipped. + */ + bool skip; + /** + * Set to true, when the iterator shall visit a node again + * when all it's children have been processed. + */ + bool visit_on_exit; + /** + * True, if this iterator is currently leaving the node. + */ + bool exiting; + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + /** + * Offset in the node struct for the next pointer. + */ + ptrdiff_t loc_next; + /** + * The total number of distinct nodes that have been passed so far. + */ + size_t counter; + /** + * The currently observed node. + * + * This is the same what cxIteratorCurrent() would return. + */ + void *node; + /** + * Stores a copy of the next pointer of the visited node. + * Allows freeing a node on exit without corrupting the iteration. + */ + void *node_next; + /** + * Internal stack. + * Will be automatically freed once the iterator becomes invalid. + * + * If you want to discard the iterator before, you need to manually + * call cxTreeIteratorDispose(). + */ + void **stack; + /** + * Internal capacity of the stack. + */ + size_t stack_capacity; + union { + /** + * Internal stack size. + */ + size_t stack_size; + /** + * The current depth in the tree. + */ + size_t depth; + }; +} CxTreeIterator; + +/** + * An element in a visitor queue. + */ +struct cx_tree_visitor_queue_s { + /** + * The tree node to visit. + */ + void *node; + /** + * The depth of the node. + */ + size_t depth; + /** + * The next element in the queue or @c NULL. + */ + struct cx_tree_visitor_queue_s *next; +}; + +/** + * A breadth-first tree iterator. + * + * This iterator needs to maintain a visitor queue that will be automatically + * freed once the iterator becomes invalid. + * If you want to discard the iterator before, you MUST manually call + * cxTreeVisitorDispose(). + * + * This iterator is not position-aware in a strict sense, as it does not assume + * a particular order of elements in the tree. However, the iterator keeps track + * of the number of nodes it has passed in a counter variable. + * Each node, regardless of the number of passes, is counted only once. + * + * @note Objects that are pointed to by an iterator are mutable through that + * iterator. However, if the + * underlying data structure is mutated by other means than this iterator (e.g. + * elements added or removed), the iterator becomes invalid (regardless of what + * cxIteratorValid() returns). + * + * @see CxIterator + */ +typedef struct cx_tree_visitor_s { + /** + * Base members. + */ + CX_ITERATOR_BASE; + /** + * Indicates whether the subtree below the current node shall be skipped. + */ + bool skip; + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + /** + * Offset in the node struct for the next pointer. + */ + ptrdiff_t loc_next; + /** + * The total number of distinct nodes that have been passed so far. + */ + size_t counter; + /** + * The currently observed node. + * + * This is the same what cxIteratorCurrent() would return. + */ + void *node; + /** + * The current depth in the tree. + */ + size_t depth; + /** + * The next element in the visitor queue. + */ + struct cx_tree_visitor_queue_s *queue_next; + /** + * The last element in the visitor queue. + */ + struct cx_tree_visitor_queue_s *queue_last; +} CxTreeVisitor; + +/** + * Releases internal memory of the given tree iterator. + * @param iter the iterator + */ +cx_attr_nonnull +static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { + free(iter->stack); + iter->stack = NULL; +} + +/** + * Releases internal memory of the given tree visitor. + * @param visitor the visitor + */ +cx_attr_nonnull +static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { + struct cx_tree_visitor_queue_s *q = visitor->queue_next; + while (q != NULL) { + struct cx_tree_visitor_queue_s *next = q->next; + free(q); + q = next; + } +} + +/** + * Advises the iterator to skip the subtree below the current node and + * also continues the current loop. + * + * @param iterator (@c CxTreeIterator) the iterator + */ +#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue + +/** + * Advises the visitor to skip the subtree below the current node and + * also continues the current loop. + * + * @param visitor (@c CxTreeVisitor) the visitor + */ +#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor) + +/** + * Links a node to a (new) parent. + * + * If the node has already a parent, it is unlinked, first. + * If the parent has children already, the node is @em appended to the list + * of all currently existing children. + * + * @param parent the parent node + * @param node the node that shall be linked + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @see cx_tree_unlink() + */ +cx_attr_nonnull +cx_attr_export +void cx_tree_link( + void *parent, + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Unlinks a node from its parent. + * + * If the node has no parent, this function does nothing. + * + * @param node the node that shall be unlinked from its parent + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @see cx_tree_link() + */ +cx_attr_nonnull +cx_attr_export +void cx_tree_unlink( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Macro that can be used instead of the magic value for infinite search depth. + */ +#define CX_TREE_SEARCH_INFINITE_DEPTH 0 + +/** + * Function pointer for a search function. + * + * A function of this kind shall check if the specified @p node + * contains the given @p data or if one of the children might contain + * the data. + * + * The function should use the returned integer to indicate how close the + * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. + * + * For example if a tree stores file path information, a node that is + * describing a parent directory of a filename that is searched, shall + * return a positive number to indicate that a child node might contain the + * searched item. On the other hand, if the node denotes a path that is not a + * prefix of the searched filename, the function would return -1 to indicate + * that the search does not need to be continued in that branch. + * + * @param node the node that is currently investigated + * @param data the data that is searched for + * + * @return 0 if the node contains the data, + * positive if one of the children might contain the data, + * negative if neither the node, nor the children contains the data + */ +cx_attr_nonnull +typedef int (*cx_tree_search_data_func)(const void *node, const void *data); + + +/** + * Function pointer for a search function. + * + * A function of this kind shall check if the specified @p node + * contains the same @p data as @p new_node or if one of the children might + * contain the data. + * + * The function should use the returned integer to indicate how close the + * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. + * + * For example if a tree stores file path information, a node that is + * describing a parent directory of a filename that is searched, shall + * return a positive number to indicate that a child node might contain the + * searched item. On the other hand, if the node denotes a path that is not a + * prefix of the searched filename, the function would return -1 to indicate + * that the search does not need to be continued in that branch. + * + * @param node the node that is currently investigated + * @param new_node a new node with the information which is searched + * + * @return 0 if @p node contains the same data as @p new_node, + * positive if one of the children might contain the data, + * negative if neither the node, nor the children contains the data + */ +cx_attr_nonnull +typedef int (*cx_tree_search_func)(const void *node, const void *new_node); + +/** + * Searches for data in a tree. + * + * When the data cannot be found exactly, the search function might return a + * closest result which might be a good starting point for adding a new node + * to the tree (see also #cx_tree_add()). + * + * Depending on the tree structure it is not necessarily guaranteed that the + * "closest" match is uniquely defined. This function will search for a node + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * node matching the criteria is returned. + * + * @param root the root node + * @param depth the maximum depth (zero=indefinite, one=just root) + * @param data the data to search for + * @param sfunc the search function + * @param result where the result shall be stored + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return zero if the node was found exactly, positive if a node was found that + * could contain the node (but doesn't right now), negative if the tree does not + * contain any node that might be related to the searched data + */ +cx_attr_nonnull +cx_attr_access_w(5) +cx_attr_export +int cx_tree_search_data( + const void *root, + size_t depth, + const void *data, + cx_tree_search_data_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Searches for a node in a tree. + * + * When no node with the same data can be found, the search function might + * return a closest result which might be a good starting point for adding the + * new node to the tree (see also #cx_tree_add()). + * + * Depending on the tree structure it is not necessarily guaranteed that the + * "closest" match is uniquely defined. This function will search for a node + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * node matching the criteria is returned. + * + * @param root the root node +* @param depth the maximum depth (zero=indefinite, one=just root) + * @param node the node to search for + * @param sfunc the search function + * @param result where the result shall be stored + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return zero if the node was found exactly, positive if a node was found that + * could contain the node (but doesn't right now), negative if the tree does not + * contain any node that might be related to the searched data + */ +cx_attr_nonnull +cx_attr_access_w(5) +cx_attr_export +int cx_tree_search( + const void *root, + size_t depth, + const void *node, + cx_tree_search_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Creates a depth-first iterator for a tree with the specified root node. + * + * @note A tree iterator needs to maintain a stack of visited nodes, which is + * allocated using stdlib malloc(). + * When the iterator becomes invalid, this memory is automatically released. + * However, if you wish to cancel the iteration before the iterator becomes + * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release + * the memory. + * + * @remark The returned iterator does not support cxIteratorFlagRemoval(). + * + * @param root the root node + * @param visit_on_exit set to true, when the iterator shall visit a node again + * after processing all children + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return the new tree iterator + * @see cxTreeIteratorDispose() + */ +cx_attr_nodiscard +cx_attr_export +CxTreeIterator cx_tree_iterator( + void *root, + bool visit_on_exit, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Creates a breadth-first iterator for a tree with the specified root node. + * + * @note A tree visitor needs to maintain a queue of to be visited nodes, which + * is allocated using stdlib malloc(). + * When the visitor becomes invalid, this memory is automatically released. + * However, if you wish to cancel the iteration before the visitor becomes + * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release + * the memory. + * + * @remark The returned iterator does not support cxIteratorFlagRemoval(). + * + * @param root the root node + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return the new tree visitor + * @see cxTreeVisitorDispose() + */ +cx_attr_nodiscard +cx_attr_export +CxTreeVisitor cx_tree_visitor( + void *root, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Describes a function that creates a tree node from the specified data. + * The first argument points to the data the node shall contain and + * the second argument may be used for additional data (e.g. an allocator). + * Functions of this type shall either return a new pointer to a newly + * created node or @c NULL when allocation fails. + * + * @note the function may leave the node pointers in the struct uninitialized. + * The caller is responsible to set them according to the intended use case. + */ +cx_attr_nonnull_arg(1) +typedef void *(*cx_tree_node_create_func)(const void *, void *); + +/** + * The local search depth for a new subtree when adding multiple elements. + * The default value is 3. + * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to + * implement optimized insertion of multiple elements into a tree. + */ +cx_attr_export +extern unsigned int cx_tree_add_look_around_depth; + +/** + * Adds multiple elements efficiently to a tree. + * + * Once an element cannot be added to the tree, this function returns, leaving + * the iterator in a valid state pointing to the element that could not be + * added. + * Also, the pointer of the created node will be stored to @p failed. + * The integer returned by this function denotes the number of elements obtained + * from the @p iter that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. + * + * The advantage of this function compared to multiple invocations of + * #cx_tree_add() is that the search for the insert locations is not always + * started from the root node. + * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes + * of the current insert location before starting from the root node again. + * When the variable is set to zero, only the last found location is checked + * again. + * + * Refer to the documentation of #cx_tree_add() for more details. + * + * @param iter a pointer to an arbitrary iterator + * @param num the maximum number of elements to obtain from the iterator + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param root the root node of the tree + * @param failed location where the pointer to a failed node shall be stored + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the number of nodes created and added + * @see cx_tree_add() + */ +cx_attr_nonnull_arg(1, 3, 4, 6, 7) +cx_attr_access_w(6) +cx_attr_export +size_t cx_tree_add_iter( + struct cx_iterator_base_s *iter, + size_t num, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Adds multiple elements efficiently to a tree. + * + * Once an element cannot be added to the tree, this function returns, storing + * the pointer of the created node to @p failed. + * The integer returned by this function denotes the number of elements from + * the @p src array that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. + * + * The advantage of this function compared to multiple invocations of + * #cx_tree_add() is that the search for the insert locations is not always + * started from the root node. + * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes + * of the current insert location before starting from the root node again. + * When the variable is set to zero, only the last found location is checked + * again. + * + * Refer to the documentation of #cx_tree_add() for more details. + * + * @param src a pointer to the source data array + * @param num the number of elements in the @p src array + * @param elem_size the size of each element in the @p src array + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param failed location where the pointer to a failed node shall be stored + * @param root the root node of the tree + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the number of array elements successfully processed + * @see cx_tree_add() + */ +cx_attr_nonnull_arg(1, 4, 5, 7, 8) +cx_attr_access_w(7) +cx_attr_export +size_t cx_tree_add_array( + const void *src, + size_t num, + size_t elem_size, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Adds data to a tree. + * + * An adequate location where to add the new tree node is searched with the + * specified @p sfunc. + * + * When a location is found, the @p cfunc will be invoked with @p cdata. + * + * The node returned by @p cfunc will be linked into the tree. + * When @p sfunc returned a positive integer, the new node will be linked as a + * child. The other children (now siblings of the new node) are then checked + * with @p sfunc, whether they could be children of the new node and re-linked + * accordingly. + * + * When @p sfunc returned zero and the found node has a parent, the new + * node will be added as sibling - otherwise, the new node will be added + * as a child. + * + * When @p sfunc returned a negative value, the new node will not be added to + * the tree and this function returns a non-zero value. + * The caller should check if @p cnode contains a node pointer and deal with the + * node that could not be added. + * + * This function also returns a non-zero value when @p cfunc tries to allocate + * a new node but fails to do so. In that case, the pointer stored to @p cnode + * will be @c NULL. + * + * Multiple elements can be added more efficiently with + * #cx_tree_add_array() or #cx_tree_add_iter(). + * + * @param src a pointer to the data + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param cnode the location where a pointer to the new node is stored + * @param root the root node of the tree + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return zero when a new node was created and added to the tree, + * non-zero otherwise + */ +cx_attr_nonnull_arg(1, 2, 3, 5, 6) +cx_attr_access_w(5) +cx_attr_export +int cx_tree_add( + const void *src, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **cnode, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + + +/** + * Tree class type. + */ +typedef struct cx_tree_class_s cx_tree_class; + +/** + * Base structure that can be used for tree nodes in a #CxTree. + */ +struct cx_tree_node_base_s { + /** + * Pointer to the parent. + */ + struct cx_tree_node_base_s *parent; + /** + * Pointer to the first child. + */ + struct cx_tree_node_base_s *children; + /** + * Pointer to the last child. + */ + struct cx_tree_node_base_s *last_child; + /** + * Pointer to the previous sibling. + */ + struct cx_tree_node_base_s *prev; + /** + * Pointer to the next sibling. + */ + struct cx_tree_node_base_s *next; +}; + +/** + * Structure for holding the base data of a tree. + */ +struct cx_tree_s { + /** + * The tree class definition. + */ + const cx_tree_class *cl; + + /** + * Allocator to allocate new nodes. + */ + const CxAllocator *allocator; + + /** + * A pointer to the root node. + * + * Will be @c NULL when @c size is 0. + */ + void *root; + + /** + * A function to create new nodes. + * + * Invocations to this function will receive a pointer to this tree + * structure as second argument. + * + * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to. + */ + cx_tree_node_create_func node_create; + + /** + * An optional simple destructor for the tree nodes. + */ + cx_destructor_func simple_destructor; + + /** + * An optional advanced destructor for the tree nodes. + */ + cx_destructor_func2 advanced_destructor; + + /** + * The pointer to additional data that is passed to the advanced destructor. + */ + void *destructor_data; + + /** + * A function to compare two nodes. + */ + cx_tree_search_func search; + + /** + * A function to compare a node with data. + */ + cx_tree_search_data_func search_data; + + /** + * The number of currently stored elements. + */ + size_t size; + + /** + * Offset in the node struct for the parent pointer. + */ + ptrdiff_t loc_parent; + + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + + /** + * Optional offset in the node struct for the pointer to the last child + * in the linked list (negative if there is no such pointer). + */ + ptrdiff_t loc_last_child; + + /** + * Offset in the node struct for the previous sibling pointer. + */ + ptrdiff_t loc_prev; + + /** + * Offset in the node struct for the next sibling pointer. + */ + ptrdiff_t loc_next; +}; + +/** + * Macro to roll out the #cx_tree_node_base_s structure with a custom + * node type. + * + * Must be used as first member in your custom tree struct. + * + * @param type the data type for the nodes + */ +#define CX_TREE_NODE_BASE(type) \ + type *parent; \ + type *children;\ + type *last_child;\ + type *prev;\ + type *next + +/** + * Macro for specifying the layout of a base node tree. + * + * When your tree uses #CX_TREE_NODE_BASE, you can use this + * macro in all tree functions that expect the layout parameters + * @c loc_parent, @c loc_children, @c loc_last_child, @c loc_prev, + * and @c loc_next. + */ +#define cx_tree_node_base_layout \ + offsetof(struct cx_tree_node_base_s, parent),\ + offsetof(struct cx_tree_node_base_s, children),\ + offsetof(struct cx_tree_node_base_s, last_child),\ + offsetof(struct cx_tree_node_base_s, prev), \ + offsetof(struct cx_tree_node_base_s, next) + +/** + * The class definition for arbitrary trees. + */ +struct cx_tree_class_s { + /** + * Member function for inserting a single element. + * + * Implementations SHALL NOT simply invoke @p insert_many as this comes + * with too much overhead. + */ + int (*insert_element)( + struct cx_tree_s *tree, + const void *data + ); + + /** + * Member function for inserting multiple elements. + * + * Implementations SHALL avoid to perform a full search in the tree for + * every element even though the source data MAY be unsorted. + */ + size_t (*insert_many)( + struct cx_tree_s *tree, + struct cx_iterator_base_s *iter, + size_t n + ); + + /** + * Member function for finding a node. + */ + void *(*find)( + struct cx_tree_s *tree, + const void *subtree, + const void *data, + size_t depth + ); +}; + +/** + * Common type for all tree implementations. + */ +typedef struct cx_tree_s CxTree; + + +/** + * Destroys a node and it's subtree. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * When this function is invoked on the root node of the tree, it destroys the + * tree contents, but - in contrast to #cxTreeFree() - not the tree + * structure, leaving an empty tree behind. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @attention This function will not free the memory of the nodes with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to remove + * @see cxTreeFree() + */ +cx_attr_nonnull +cx_attr_export +void cxTreeDestroySubtree(CxTree *tree, void *node); + + +/** + * Destroys the tree contents. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * This is a convenience macro for invoking #cxTreeDestroySubtree() on the + * root node of the tree. + * + * @attention Be careful when calling this function when no destructor function + * is registered that actually frees the memory of nodes. In that case you will + * need a reference to the (former) root node of the tree somewhere or + * otherwise you will be leaking memory. + * + * @param tree the tree + * @see cxTreeDestroySubtree() + */ +#define cxTreeClear(tree) cxTreeDestroySubtree(tree, tree->root) + +/** + * Deallocates the tree structure. + * + * The destructor functions are invoked for each node, starting with the leaf + * nodes. + * It is guaranteed that for each node the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will only invoke the destructor functions + * on the nodes. + * It will NOT additionally free the nodes with the tree's allocator, because + * that would cause a double-free in most scenarios where the advanced + * destructor is already freeing the memory. + * + * @param tree the tree to free + */ +cx_attr_export +void cxTreeFree(CxTree *tree); + +/** + * Creates a new tree structure based on the specified layout. + * + * The specified @p allocator will be used for creating the tree struct + * and SHALL be used by @p create_func to allocate memory for the nodes. + * + * @note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator the allocator that shall be used + * (if @c NULL, a default stdlib allocator will be used) + * @param create_func a function that creates new nodes + * @param search_func a function that compares two nodes + * @param search_data_func a function that compares a node with data + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreateSimple() + * @see cxTreeCreateWrapped() + */ +cx_attr_nonnull_arg(2, 3, 4) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) +cx_attr_export +CxTree *cxTreeCreate( + const CxAllocator *allocator, + cx_tree_node_create_func create_func, + cx_tree_search_func search_func, + cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Creates a new tree structure based on a default layout. + * + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first + * member (or at least respect the default offsets specified in the tree + * struct) and they MUST be allocated with the specified allocator. + * + * @note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator (@c CxAllocator*) the allocator that shall be used + * @param create_func (@c cx_tree_node_create_func) a function that creates new nodes + * @param search_func (@c cx_tree_search_func) a function that compares two nodes + * @param search_data_func (@c cx_tree_search_data_func) a function that compares a node with data + * @return (@c CxTree*) the new tree + * @see cxTreeCreate() + */ +#define cxTreeCreateSimple(\ + allocator, create_func, search_func, search_data_func \ +) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ +cx_tree_node_base_layout) + +/** + * Creates a new tree structure based on an existing tree. + * + * The specified @p allocator will be used for creating the tree struct. + * + * @attention This function will create an incompletely defined tree structure + * where neither the create function, the search function, nor a destructor + * will be set. If you wish to use any of this functionality for the wrapped + * tree, you need to specify those functions afterwards. + * + * @param allocator the allocator that was used for nodes of the wrapped tree + * (if @c NULL, a default stdlib allocator is assumed) + * @param root the root node of the tree that shall be wrapped + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreate() + */ +cx_attr_nonnull_arg(2) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) +cx_attr_export +CxTree *cxTreeCreateWrapped( + const CxAllocator *allocator, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Inserts data into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to insert + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +static inline int cxTreeInsert( + CxTree *tree, + const void *data +) { + return tree->cl->insert_element(tree, data); +} + +/** + * Inserts elements provided by an iterator efficiently into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param iter the iterator providing the elements + * @param n the maximum number of elements to insert + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertIter( + CxTree *tree, + CxIteratorBase *iter, + size_t n +) { + return tree->cl->insert_many(tree, iter, n); +} + +/** + * Inserts an array of data efficiently into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the array of data to insert + * @param elem_size the size of each element in the array + * @param n the number of elements in the array + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertArray( + CxTree *tree, + const void *data, + size_t elem_size, + size_t n +) { + if (n == 0) return 0; + if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; + CxIterator iter = cxIterator(data, elem_size, n); + return cxTreeInsertIter(tree, cxIteratorRef(iter), n); +} + +/** + * Searches the data in the specified tree. + * + * @remark For this function to work, the tree needs a specified @c search_data + * function, which might not be available wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @return the first matching node, or @c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFind( + CxTree *tree, + const void *data +) { + return tree->cl->find(tree, tree->root, data, 0); +} + +/** + * Searches the data in the specified subtree. + * + * When @p max_depth is zero, the depth is not limited. + * The @p subtree_root itself is on depth 1 and its children have depth 2. + * + * @note When @p subtree_root is not part of the @p tree, the behavior is + * undefined. + * + * @remark For this function to work, the tree needs a specified @c search_data + * function, which might not be the case for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @param subtree_root the node where to start + * @param max_depth the maximum search depth + * @return the first matching node, or @c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFindInSubtree( + CxTree *tree, + const void *data, + void *subtree_root, + size_t max_depth +) { + return tree->cl->find(tree, subtree_root, data, max_depth); +} + +/** + * Determines the size of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the number of nodes in the specified subtree + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the tree depth including the @p subtree_root + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the entire tree. + * + * @param tree the tree + * @return the tree depth, counting the root as one + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +size_t cxTreeDepth(CxTree *tree); + +/** + * Creates a depth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterateSubtree( + CxTree *tree, + void *node, + bool visit_on_exit +) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a breadth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a depth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterate( + CxTree *tree, + bool visit_on_exit +) { + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); +} + +/** + * Creates a breadth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); +} + +/** + * Sets the (new) parent of the specified child. + * + * If the @p child is not already member of the tree, this function behaves + * as #cxTreeAddChildNode(). + * + * @param tree the tree + * @param parent the (new) parent of the child + * @param child the node to add + * @see cxTreeAddChildNode() + */ +cx_attr_nonnull +cx_attr_export +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +); + +/** + * Adds a new node to the tree. + * + * If the @p child is already member of the tree, the behavior is undefined. + * Use #cxTreeSetParent() if you want to move a subtree to another location. + * + * @attention The node may be externally created, but MUST obey the same rules + * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * the same allocator). + * + * @param tree the tree + * @param parent the parent of the node to add + * @param child the node to add + * @see cxTreeSetParent() + */ +cx_attr_nonnull +cx_attr_export +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +); + +/** + * Creates a new node and adds it to the tree. + * + * With this function you can decide where exactly the new node shall be added. + * If you specified an appropriate search function, you may want to consider + * leaving this task to the tree by using #cxTreeInsert(). + * + * Be aware that adding nodes at arbitrary locations in the tree might cause + * wrong or undesired results when subsequently invoking #cxTreeInsert() and + * the invariant imposed by the search function does not hold any longer. + * + * @param tree the tree + * @param parent the parent node of the new node + * @param data the data that will be submitted to the create function + * @return zero when the new node was created, non-zero on allocation failure + * @see cxTreeInsert() + */ +cx_attr_nonnull +cx_attr_export +int cxTreeAddChild( + CxTree *tree, + void *parent, + const void *data +); + +/** + * A function that is invoked when a node needs to be re-linked to a new parent. + * + * When a node is re-linked, sometimes the contents need to be updated. + * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode() + * so that those updates can be applied when re-linking the children of the + * removed node. + * + * @param node the affected node + * @param old_parent the old parent of the node + * @param new_parent the new parent of the node + */ +cx_attr_nonnull +typedef void (*cx_tree_relink_func)( + void *node, + const void *old_parent, + const void *new_parent +); + +/** + * Removes a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed node by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if @p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +cx_attr_export +int cxTreeRemoveNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + +/** + * Removes a node and it's subtree from the tree. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove + */ +cx_attr_nonnull +cx_attr_export +void cxTreeRemoveSubtree(CxTree *tree, void *node); + +/** + * Destroys a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will not free the memory of the node with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to destroy (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if @p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +cx_attr_export +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //UCX_TREE_H
--- a/src/ucx/cx/utils.h Mon Feb 10 17:44:51 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * \file utils.h - * - * \brief General purpose utility functions. - * - * \author Mike Becker - * \author Olaf Wintermann - * \version 3.0 - * \copyright 2-Clause BSD License - */ - -#ifndef UCX_UTILS_H -#define UCX_UTILS_H - -#include "common.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Convenience macro for a for loop that counts from zero to n-1. - */ -#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++) - -/** - * Convenience macro for swapping two pointers. - */ -#ifdef __cplusplus -#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) -#else -#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) -#endif - -// cx_szmul() definition - -#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) -#define CX_SZMUL_BUILTIN - -/** - * Alias for \c __builtin_mul_overflow. - * - * Performs a multiplication of size_t values and checks for overflow. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t, where the result should - * be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) - -#else // no GNUC or clang bultin - -/** - * Performs a multiplication of size_t values and checks for overflow. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t, where the result should - * be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) - -/** - * Performs a multiplication of size_t values and checks for overflow. - * - * This is a custom implementation in case there is no compiler builtin - * available. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t where the result should be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -int cx_szmul_impl(size_t a, size_t b, size_t *result); - -#endif // cx_szmul - - -/** - * Reads data from a stream and writes it to another stream. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or \c NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can - * set this to zero to let the implementation decide - * @param n the maximum number of bytes that shall be copied. - * If this is larger than \p bufsize, the content is copied over multiple - * iterations. - * @return the total number of bytes copied - */ -__attribute__((__nonnull__(1, 2, 3, 4))) -size_t cx_stream_bncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - char *buf, - size_t bufsize, - size_t n -); - -/** - * Reads data from a stream and writes it to another stream. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or \c NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can - * set this to zero to let the implementation decide - * @return total number of bytes copied - */ -#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ - cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) - -/** - * Reads data from a stream and writes it to another stream. - * - * The data is temporarily stored in a stack allocated buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param n the maximum number of bytes that shall be copied. - * @return total number of bytes copied - */ -__attribute__((__nonnull__)) -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -); - -/** - * Reads data from a stream and writes it to another stream. - * - * The data is temporarily stored in a stack allocated buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @return total number of bytes copied - */ -#define cx_stream_copy(src, dest, rfnc, wfnc) \ - cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) - -#ifdef __cplusplus -} -#endif - -#endif // UCX_UTILS_H
--- a/src/ucx/hash_key.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/hash_key.c Sun Mar 02 18:10:52 2025 +0100 @@ -30,7 +30,7 @@ #include <string.h> void cx_hash_murmur(CxHashKey *key) { - unsigned char const *data = key->data; + const unsigned char *data = key->data; if (data == NULL) { // extension: special value for NULL key->hash = 1574210520u; @@ -40,7 +40,7 @@ unsigned m = 0x5bd1e995; unsigned r = 24; - unsigned h = 25 ^ len; + unsigned h = 25 ^ (unsigned) len; unsigned i = 0; while (len >= 4) { unsigned k = data[i + 0] & 0xFF; @@ -81,7 +81,7 @@ key->hash = h; } -CxHashKey cx_hash_key_str(char const *str) { +CxHashKey cx_hash_key_str(const char *str) { CxHashKey key; key.data = str; key.len = str == NULL ? 0 : strlen(str); @@ -90,7 +90,7 @@ } CxHashKey cx_hash_key_bytes( - unsigned char const *bytes, + const unsigned char *bytes, size_t len ) { CxHashKey key; @@ -101,7 +101,7 @@ } CxHashKey cx_hash_key( - void const *obj, + const void *obj, size_t len ) { CxHashKey key;
--- a/src/ucx/hash_map.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/hash_map.c Sun Mar 02 18:10:52 2025 +0100 @@ -27,10 +27,10 @@ */ #include "cx/hash_map.h" -#include "cx/utils.h" #include <string.h> #include <assert.h> +#include <errno.h> struct cx_hash_map_element_s { /** A pointer to the next element in the current bucket. */ @@ -45,7 +45,7 @@ static void cx_hash_map_clear(struct cx_map_s *map) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - cx_for_n(i, hash_map->bucket_count) { + for (size_t i = 0; i < hash_map->bucket_count; i++) { struct cx_hash_map_element_s *elem = hash_map->buckets[i]; if (elem != NULL) { do { @@ -53,9 +53,9 @@ // invoke the destructor cx_invoke_destructor(map, elem->data); // free the key data - cxFree(map->allocator, (void *) elem->key.data); + cxFree(map->collection.allocator, (void *) elem->key.data); // free the node - cxFree(map->allocator, elem); + cxFree(map->collection.allocator, elem); // proceed elem = next; } while (elem != NULL); @@ -64,7 +64,7 @@ hash_map->buckets[i] = NULL; } } - map->size = 0; + map->collection.size = 0; } static void cx_hash_map_destructor(struct cx_map_s *map) { @@ -72,10 +72,10 @@ // free the buckets cx_hash_map_clear(map); - cxFree(map->allocator, hash_map->buckets); + cxFree(map->collection.allocator, hash_map->buckets); // free the map structure - cxFree(map->allocator, map); + cxFree(map->collection.allocator, map); } static int cx_hash_map_put( @@ -84,7 +84,7 @@ void *value ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - CxAllocator const *allocator = map->allocator; + const CxAllocator *allocator = map->collection.allocator; unsigned hash = key.hash; if (hash == 0) { @@ -103,34 +103,31 @@ if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && memcmp(elm->key.data, key.data, key.len) == 0) { - // overwrite existing element - if (map->store_pointer) { + // overwrite existing element, but call destructors first + cx_invoke_destructor(map, elm->data); + if (map->collection.store_pointer) { memcpy(elm->data, &value, sizeof(void *)); } else { - memcpy(elm->data, value, map->item_size); + memcpy(elm->data, value, map->collection.elem_size); } } else { // allocate new element struct cx_hash_map_element_s *e = cxMalloc( allocator, - sizeof(struct cx_hash_map_element_s) + map->item_size + sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) { - return -1; - } + if (e == NULL) return -1; // write the value - if (map->store_pointer) { + if (map->collection.store_pointer) { memcpy(e->data, &value, sizeof(void *)); } else { - memcpy(e->data, value, map->item_size); + memcpy(e->data, value, map->collection.elem_size); } // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) { - return -1; - } + if (kd == NULL) return -1; memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -145,7 +142,7 @@ e->next = elm; // increase the size - map->size++; + map->collection.size++; } return 0; @@ -164,26 +161,37 @@ prev->next = elm->next; } // free element - cxFree(hash_map->base.allocator, (void *) elm->key.data); - cxFree(hash_map->base.allocator, elm); + cxFree(hash_map->base.collection.allocator, (void *) elm->key.data); + cxFree(hash_map->base.collection.allocator, elm); // decrease size - hash_map->base.size--; + hash_map->base.collection.size--; } /** * Helper function to avoid code duplication. * + * If @p remove is true, and @p targetbuf is @c NULL, the element + * will be destroyed when found. + * + * If @p remove is true, and @p targetbuf is set, the element will + * be copied to that buffer and no destructor function is called. + * + * If @p remove is false, @p targetbuf must not be non-null and + * either the pointer, when the map is storing pointers, is copied + * to the target buffer, or a pointer to the stored object will + * be copied to the target buffer. + * * @param map the map * @param key the key to look up + * @param targetbuf see description * @param remove flag indicating whether the looked up entry shall be removed - * @param destroy flag indicating whether the destructor shall be invoked - * @return a pointer to the value corresponding to the key or \c NULL + * @return zero, if the key was found, non-zero otherwise */ -static void *cx_hash_map_get_remove( +static int cx_hash_map_get_remove( CxMap *map, CxHashKey key, - bool remove, - bool destroy + void *targetbuf, + bool remove ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; @@ -199,82 +207,87 @@ while (elm && elm->key.hash <= hash) { if (elm->key.hash == hash && elm->key.len == key.len) { if (memcmp(elm->key.data, key.data, key.len) == 0) { - void *data = NULL; - if (destroy) { - cx_invoke_destructor(map, elm->data); + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); + } else { + memcpy(targetbuf, elm->data, map->collection.elem_size); + } + cx_hash_map_unlink(hash_map, slot, prev, elm); } else { - if (map->store_pointer) { + assert(targetbuf != NULL); + void *data = NULL; + if (map->collection.store_pointer) { data = *(void **) elm->data; } else { data = elm->data; } + memcpy(targetbuf, &data, sizeof(void *)); } - if (remove) { - cx_hash_map_unlink(hash_map, slot, prev, elm); - } - return data; + return 0; } } prev = elm; elm = prev->next; } - return NULL; + return 1; } static void *cx_hash_map_get( - CxMap const *map, + const CxMap *map, CxHashKey key ) { // we can safely cast, because we know the map stays untouched - return cx_hash_map_get_remove((CxMap *) map, key, false, false); + void *ptr = NULL; + int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false); + return found == 0 ? ptr : NULL; } -static void *cx_hash_map_remove( +static int cx_hash_map_remove( CxMap *map, CxHashKey key, - bool destroy + void *targetbuf ) { - return cx_hash_map_get_remove(map, key, true, destroy); + return cx_hash_map_get_remove(map, key, targetbuf, true); } -static void *cx_hash_map_iter_current_entry(void const *it) { - struct cx_iterator_s const *iter = it; - // struct has to have a compatible signature - return (struct cx_map_entry_s *) &(iter->kv_data); +static void *cx_hash_map_iter_current_entry(const void *it) { + const CxMapIterator *iter = it; + // we have to cast away const, because of the signature + return (void*) &iter->entry; } -static void *cx_hash_map_iter_current_key(void const *it) { - struct cx_iterator_s const *iter = it; - struct cx_hash_map_element_s *elm = iter->elem_handle; +static void *cx_hash_map_iter_current_key(const void *it) { + const CxMapIterator *iter = it; + struct cx_hash_map_element_s *elm = iter->elem; return &elm->key; } -static void *cx_hash_map_iter_current_value(void const *it) { - struct cx_iterator_s const *iter = it; - struct cx_hash_map_s const *map = iter->src_handle; - struct cx_hash_map_element_s *elm = iter->elem_handle; - if (map->base.store_pointer) { +static void *cx_hash_map_iter_current_value(const void *it) { + const CxMapIterator *iter = it; + const CxMap *map = iter->map.c; + struct cx_hash_map_element_s *elm = iter->elem; + if (map->collection.store_pointer) { return *(void **) elm->data; } else { return elm->data; } } -static bool cx_hash_map_iter_valid(void const *it) { - struct cx_iterator_s const *iter = it; - return iter->elem_handle != NULL; +static bool cx_hash_map_iter_valid(const void *it) { + const CxMapIterator *iter = it; + return iter->elem != NULL; } static void cx_hash_map_iter_next(void *it) { - struct cx_iterator_s *iter = it; - struct cx_hash_map_element_s *elm = iter->elem_handle; + CxMapIterator *iter = it; + CxMap *map = iter->map.m; + struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map; + struct cx_hash_map_element_s *elm = iter->elem; // remove current element, if asked if (iter->base.remove) { - // obtain mutable pointer to the map - struct cx_mut_iterator_s *miter = it; - struct cx_hash_map_s *map = miter->src_handle; // clear the flag iter->base.remove = false; @@ -284,18 +297,18 @@ // search the previous element struct cx_hash_map_element_s *prev = NULL; - if (map->buckets[iter->slot] != elm) { - prev = map->buckets[iter->slot]; + if (hmap->buckets[iter->slot] != elm) { + prev = hmap->buckets[iter->slot]; while (prev->next != elm) { prev = prev->next; } } // destroy - cx_invoke_destructor((struct cx_map_s *) map, elm->data); + cx_invoke_destructor(map, elm->data); // unlink - cx_hash_map_unlink(map, iter->slot, prev, elm); + cx_hash_map_unlink(hmap, iter->slot, prev, elm); // advance elm = next; @@ -306,84 +319,73 @@ } // search the next bucket, if required - struct cx_hash_map_s const *map = iter->src_handle; - while (elm == NULL && ++iter->slot < map->bucket_count) { - elm = map->buckets[iter->slot]; + while (elm == NULL && ++iter->slot < hmap->bucket_count) { + elm = hmap->buckets[iter->slot]; } + iter->elem = elm; - // fill the struct with the next element - iter->elem_handle = elm; - if (elm == NULL) { - iter->kv_data.key = NULL; - iter->kv_data.value = NULL; - } else { - iter->kv_data.key = &elm->key; - if (map->base.store_pointer) { - iter->kv_data.value = *(void **) elm->data; + // copy data to a location where the iterator can point to + // we need to do it here, because the iterator function call + // must not modify the iterator (the parameter is const) + if (elm != NULL) { + iter->entry.key = &elm->key; + if (iter->map.c->collection.store_pointer) { + iter->entry.value = *(void **) elm->data; } else { - iter->kv_data.value = elm->data; + iter->entry.value = elm->data; } } } -static bool cx_hash_map_iter_flag_rm(void *it) { - struct cx_iterator_base_s *iter = it; - if (iter->mutating) { - iter->remove = true; - return true; - } else { - return false; - } -} - -static CxIterator cx_hash_map_iterator( - CxMap const *map, +static CxMapIterator cx_hash_map_iterator( + const CxMap *map, enum cx_map_iterator_type type ) { - CxIterator iter; + CxMapIterator iter; - iter.src_handle = map; - iter.base.valid = cx_hash_map_iter_valid; - iter.base.next = cx_hash_map_iter_next; + iter.map.c = map; + iter.elem_count = map->collection.size; switch (type) { case CX_MAP_ITERATOR_PAIRS: + iter.elem_size = sizeof(CxMapEntry); iter.base.current = cx_hash_map_iter_current_entry; break; case CX_MAP_ITERATOR_KEYS: + iter.elem_size = sizeof(CxHashKey); iter.base.current = cx_hash_map_iter_current_key; break; case CX_MAP_ITERATOR_VALUES: + iter.elem_size = map->collection.elem_size; iter.base.current = cx_hash_map_iter_current_value; break; default: - assert(false); + assert(false); // LCOV_EXCL_LINE } - iter.base.flag_removal = cx_hash_map_iter_flag_rm; + iter.base.valid = cx_hash_map_iter_valid; + iter.base.next = cx_hash_map_iter_next; iter.base.remove = false; iter.base.mutating = false; iter.slot = 0; iter.index = 0; - if (map->size > 0) { + if (map->collection.size > 0) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; struct cx_hash_map_element_s *elm = hash_map->buckets[0]; while (elm == NULL) { elm = hash_map->buckets[++iter.slot]; } - iter.elem_handle = elm; - iter.kv_data.key = &elm->key; - if (map->store_pointer) { - iter.kv_data.value = *(void **) elm->data; + iter.elem = elm; + iter.entry.key = &elm->key; + if (map->collection.store_pointer) { + iter.entry.value = *(void **) elm->data; } else { - iter.kv_data.value = elm->data; + iter.entry.value = elm->data; } } else { - iter.elem_handle = NULL; - iter.kv_data.key = NULL; - iter.kv_data.value = NULL; + iter.elem = NULL; } return iter; @@ -399,10 +401,14 @@ }; CxMap *cxHashMapCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, size_t itemsize, size_t buckets ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (buckets == 0) { // implementation defined default buckets = 16; @@ -416,21 +422,20 @@ map->bucket_count = buckets; map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *)); - if (map->buckets == NULL) { + if (map->buckets == NULL) { // LCOV_EXCL_START cxFree(allocator, map); return NULL; - } + } // LCOV_EXCL_STOP // initialize base members map->base.cl = &cx_hash_map_class; - map->base.allocator = allocator; + map->base.collection.allocator = allocator; if (itemsize > 0) { - map->base.store_pointer = false; - map->base.item_size = itemsize; + map->base.collection.elem_size = itemsize; } else { - map->base.store_pointer = true; - map->base.item_size = sizeof(void *); + map->base.collection.elem_size = sizeof(void *); + map->base.collection.store_pointer = true; } return (CxMap *) map; @@ -438,20 +443,22 @@ int cxMapRehash(CxMap *map) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - if (map->size > ((hash_map->bucket_count * 3) >> 2)) { + if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) { - size_t new_bucket_count = (map->size * 5) >> 1; + size_t new_bucket_count = (map->collection.size * 5) >> 1; + if (new_bucket_count < hash_map->bucket_count) { + errno = EOVERFLOW; + return 1; + } struct cx_hash_map_element_s **new_buckets = cxCalloc( - map->allocator, + map->collection.allocator, new_bucket_count, sizeof(struct cx_hash_map_element_s *) ); - if (new_buckets == NULL) { - return 1; - } + if (new_buckets == NULL) return 1; // iterate through the elements and assign them to their new slots - cx_for_n(slot, hash_map->bucket_count) { + for (size_t slot = 0; slot < hash_map->bucket_count; slot++) { struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; while (elm != NULL) { struct cx_hash_map_element_s *next = elm->next; @@ -482,7 +489,7 @@ // assign result to the map hash_map->bucket_count = new_bucket_count; - cxFree(map->allocator, hash_map->buckets); + cxFree(map->collection.allocator, hash_map->buckets); hash_map->buckets = new_buckets; } return 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/iterator.c Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/iterator.h" + +#include <string.h> + +static bool cx_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->index < iter->elem_count; +} + +static void *cx_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle; +} + +static void *cx_iter_current_ptr(const void *it) { + const struct cx_iterator_s *iter = it; + return *(void**)iter->elem_handle; +} + +static void cx_iter_next_fast(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + iter->elem_count--; + // only move the last element when we are not currently aiming + // at the last element already + if (iter->index < iter->elem_count) { + void *last = ((char *) iter->src_handle.m) + + iter->elem_count * iter->elem_size; + memcpy(iter->elem_handle, last, iter->elem_size); + } + } else { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; + } +} + +static void cx_iter_next_slow(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + iter->elem_count--; + + // number of elements to move + size_t remaining = iter->elem_count - iter->index; + if (remaining > 0) { + memmove( + iter->elem_handle, + ((char *) iter->elem_handle) + iter->elem_size, + remaining * iter->elem_size + ); + } + } else { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; + } +} + +CxIterator cxMutIterator( + void *array, + size_t elem_size, + size_t elem_count, + bool remove_keeps_order +) { + CxIterator iter; + + iter.index = 0; + iter.src_handle.m = array; + iter.elem_handle = array; + iter.elem_size = elem_size; + iter.elem_count = array == NULL ? 0 : elem_count; + iter.base.valid = cx_iter_valid; + iter.base.current = cx_iter_current; + iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast; + iter.base.remove = false; + iter.base.mutating = true; + + return iter; +} + +CxIterator cxIterator( + const void *array, + size_t elem_size, + size_t elem_count +) { + CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false); + iter.base.mutating = false; + return iter; +} + +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +) { + CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order); + iter.base.current = cx_iter_current_ptr; + return iter; +} + +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +) { + CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false); + iter.base.mutating = false; + return iter; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/json.c Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,1447 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/json.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> +#include <inttypes.h> + +/* + * RFC 8259 + * https://tools.ietf.org/html/rfc8259 + */ + +static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; + +static int json_cmp_objvalue(const void *l, const void *r) { + const CxJsonObjValue *left = l; + const CxJsonObjValue *right = r; + return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); +} + +static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { + assert(obj->type == CX_JSON_OBJECT); + CxJsonObjValue kv_dummy; + kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); + size_t index = cx_array_binary_search( + obj->value.object.values, + obj->value.object.values_size, + sizeof(CxJsonObjValue), + &kv_dummy, + json_cmp_objvalue + ); + if (index == obj->value.object.values_size) { + return NULL; + } else { + return &obj->value.object.values[index]; + } +} + +static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { + assert(objv->type == CX_JSON_OBJECT); + const CxAllocator * const al = objv->allocator; + CxJsonObject *obj = &(objv->value.object); + + // determine the index where we need to insert the new member + size_t index = cx_array_binary_search_sup( + obj->values, + obj->values_size, + sizeof(CxJsonObjValue), + &member, json_cmp_objvalue + ); + + // is the name already present? + if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { + // free the original value + cx_strfree_a(al, &obj->values[index].name); + cxJsonValueFree(obj->values[index].value); + // replace the item + obj->values[index] = member; + + // nothing more to do + return 0; + } + + // determine the old capacity and reserve for one more element + CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); + size_t oldcap = obj->values_capacity; + if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; + + // check the new capacity, if we need to realloc the index array + size_t newcap = obj->values_capacity; + if (newcap > oldcap) { + if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { + return 1; + } + } + + // check if append or insert + if (index < obj->values_size) { + // move the other elements + memmove( + &obj->values[index+1], + &obj->values[index], + (obj->values_size - index) * sizeof(CxJsonObjValue) + ); + // increase indices for the moved elements + for (size_t i = 0; i < obj->values_size ; i++) { + if (obj->indices[i] >= index) { + obj->indices[i]++; + } + } + } + + // insert the element and set the index + obj->values[index] = member; + obj->indices[obj->values_size] = index; + obj->values_size++; + + return 0; +} + +static void token_destroy(CxJsonToken *token) { + if (token->allocated) { + cx_strfree(&token->content); + } +} + +static bool json_isdigit(char c) { + // TODO: remove once UCX has public API for this + return c >= '0' && c <= '9'; +} + +static bool json_isspace(char c) { + // TODO: remove once UCX has public API for this + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; +} + +static int num_isexp(const char *content, size_t length, size_t pos) { + if (pos >= length) { + return 0; + } + + int ok = 0; + for (size_t i = pos; i < length; i++) { + char c = content[i]; + if (json_isdigit(c)) { + ok = 1; + } else if (i == pos) { + if (!(c == '+' || c == '-')) { + return 0; + } + } else { + return 0; + } + } + + return ok; +} + +static CxJsonTokenType token_numbertype(const char *content, size_t length) { + if (length == 0) return CX_JSON_TOKEN_ERROR; + + if (content[0] != '-' && !json_isdigit(content[0])) { + return CX_JSON_TOKEN_ERROR; + } + + CxJsonTokenType type = CX_JSON_TOKEN_INTEGER; + for (size_t i = 1; i < length; i++) { + if (content[i] == '.') { + if (type == CX_JSON_TOKEN_NUMBER) { + return CX_JSON_TOKEN_ERROR; // more than one decimal separator + } + type = CX_JSON_TOKEN_NUMBER; + } else if (content[i] == 'e' || content[i] == 'E') { + return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; + } else if (!json_isdigit(content[i])) { + return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep + } + } + + return type; +} + +static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { + cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); + bool allocated = false; + if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { + allocated = true; + str = cx_strcat_m(json->uncompleted.content, 1, str); + if (str.ptr == NULL) { // LCOV_EXCL_START + return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; + } // LCOV_EXCL_STOP + } + json->uncompleted = (CxJsonToken){0}; + CxJsonTokenType ttype; + if (isstring) { + ttype = CX_JSON_TOKEN_STRING; + } else { + cxstring s = cx_strcast(str); + if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) + || !cx_strcmp(s, CX_STR("null"))) { + ttype = CX_JSON_TOKEN_LITERAL; + } else { + ttype = token_numbertype(str.ptr, str.length); + } + } + if (ttype == CX_JSON_TOKEN_ERROR) { + if (allocated) { + cx_strfree(&str); + } + return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; + } + return (CxJsonToken){ttype, allocated, str}; +} + +static CxJsonTokenType char2ttype(char c) { + switch (c) { + case '[': { + return CX_JSON_TOKEN_BEGIN_ARRAY; + } + case '{': { + return CX_JSON_TOKEN_BEGIN_OBJECT; + } + case ']': { + return CX_JSON_TOKEN_END_ARRAY; + } + case '}': { + return CX_JSON_TOKEN_END_OBJECT; + } + case ':': { + return CX_JSON_TOKEN_NAME_SEPARATOR; + } + case ',': { + return CX_JSON_TOKEN_VALUE_SEPARATOR; + } + case '"': { + return CX_JSON_TOKEN_STRING; + } + default: { + if (json_isspace(c)) { + return CX_JSON_TOKEN_SPACE; + } + } + } + return CX_JSON_NO_TOKEN; +} + +static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { + // check if there is data in the buffer + if (cxBufferEof(&json->buffer)) { + return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? + CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; + } + + // current token type and start index + CxJsonTokenType ttype = json->uncompleted.tokentype; + size_t token_part_start = json->buffer.pos; + + bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING + && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; + + for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { + char c = json->buffer.space[i]; + if (ttype != CX_JSON_TOKEN_STRING) { + // currently non-string token + CxJsonTokenType ctype = char2ttype(c); // start of new token? + if (ttype == CX_JSON_NO_TOKEN) { + if (ctype == CX_JSON_TOKEN_SPACE) { + json->buffer.pos++; + continue; + } else if (ctype == CX_JSON_TOKEN_STRING) { + // begin string + ttype = CX_JSON_TOKEN_STRING; + token_part_start = i; + } else if (ctype != CX_JSON_NO_TOKEN) { + // single-char token + json->buffer.pos = i + 1; + *result = (CxJsonToken){ctype, false, {NULL, 0}}; + return CX_JSON_NO_ERROR; + } else { + ttype = CX_JSON_TOKEN_LITERAL; // number or literal + token_part_start = i; + } + } else { + // finish token + if (ctype != CX_JSON_NO_TOKEN) { + *result = token_create(json, false, token_part_start, i); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + if (result->tokentype == CX_JSON_TOKEN_ERROR) { + return CX_JSON_FORMAT_ERROR_NUMBER; + } + json->buffer.pos = i; + return CX_JSON_NO_ERROR; + } + } + } else { + // currently inside a string + if (escape_end_of_string) { + escape_end_of_string = false; + } else { + if (c == '"') { + *result = token_create(json, true, token_part_start, i + 1); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->buffer.pos = i + 1; + return CX_JSON_NO_ERROR; + } else if (c == '\\') { + escape_end_of_string = true; + } + } + } + } + + if (ttype != CX_JSON_NO_TOKEN) { + // uncompleted token + size_t uncompleted_len = json->buffer.size - token_part_start; + if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { + // current token is uncompleted + // save current token content + CxJsonToken uncompleted = { + ttype, true, + cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) + }; + if (uncompleted.content.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted = uncompleted; + } else { + // previously we also had an uncompleted token + // combine the uncompleted token with the current token + assert(json->uncompleted.allocated); + cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, + cx_strn(json->buffer.space + token_part_start, uncompleted_len)); + if (str.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted.content = str; + } + // advance the buffer position - we saved the stuff in the uncompleted token + json->buffer.pos += uncompleted_len; + } + + return CX_JSON_INCOMPLETE_DATA; +} + +// converts a Unicode codepoint to utf8 +static unsigned codepoint_to_utf8(uint32_t codepoint, char *output_buf) { + if (codepoint <= 0x7F) { + *output_buf = (char)codepoint; + return 1; + } else if (codepoint <= 0x7FF) { + output_buf[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); + output_buf[1] = (char)(0x80 | (codepoint & 0x3F)); + return 2; + } else if (codepoint <= 0xFFFF) { + output_buf[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); + output_buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + output_buf[2] = (char)(0x80 | (codepoint & 0x3F)); + return 3; + } else if (codepoint <= 0x10FFFF) { + output_buf[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); + output_buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + output_buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + output_buf[3] = (char)(0x80 | (codepoint & 0x3F)); + return 4; + } + + return 0; // LCOV_EXCL_LINE +} + +// converts a utf16 surrogate pair to utf8 +static inline uint32_t utf16pair_to_codepoint(uint16_t c0, uint16_t c1) { + return ((c0 - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000; +} + +static unsigned unescape_unicode_string(cxstring str, char *utf8buf) { + // str is supposed to start with "\uXXXX" or "\uXXXX\uXXXX" + // remaining bytes in the string are ignored (str may be larger!) + + if (str.length < 6 || str.ptr[0] != '\\' || str.ptr[1] != 'u') { + return 0; + } + + unsigned utf8len = 0; + cxstring ustr1 = { str.ptr + 2, 4}; + uint16_t utf16a, utf16b; + if (!cx_strtou16_lc(ustr1, &utf16a, 16, "")) { + uint32_t codepoint; + if (utf16a < 0xD800 || utf16a > 0xE000) { + // character is in the Basic Multilingual Plane + // and encoded as a single utf16 char + codepoint = utf16a; + utf8len = codepoint_to_utf8(codepoint, utf8buf); + } else if (utf16a >= 0xD800 && utf16a <= 0xDBFF) { + // character is encoded as a surrogate pair + // get next 6 bytes + if (str.length >= 12) { + if (str.ptr[6] == '\\' && str.ptr[7] == 'u') { + cxstring ustr2 = { str.ptr+8, 4 }; + if (!cx_strtou16_lc(ustr2, &utf16b, 16, "") + && utf16b >= 0xDC00 && utf16b <= 0xDFFF) { + codepoint = utf16pair_to_codepoint(utf16a, utf16b); + utf8len = codepoint_to_utf8(codepoint, utf8buf); + } + } + } + } + } + return utf8len; +} + +static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { + // note: this function expects that str contains the enclosing quotes! + + cxmutstr result; + result.length = 0; + result.ptr = cxMalloc(a, str.length - 1); + if (result.ptr == NULL) return result; // LCOV_EXCL_LINE + + bool u = false; + for (size_t i = 1; i < str.length - 1; i++) { + char c = str.ptr[i]; + if (u) { + u = false; + if (c == 'n') { + c = '\n'; + } else if (c == '"') { + c = '"'; + } else if (c == 't') { + c = '\t'; + } else if (c == 'r') { + c = '\r'; + } else if (c == '\\') { + c = '\\'; + } else if (c == '/') { + c = '/'; // always unescape, we don't need settings here + } else if (c == 'f') { + c = '\f'; + } else if (c == 'b') { + c = '\b'; + } else if (c == 'u') { + char utf8buf[4]; + unsigned utf8len = unescape_unicode_string( + cx_strn(str.ptr + i - 1, str.length + 1 - i), + utf8buf + ); + if(utf8len > 0) { + i += utf8len < 4 ? 4 : 10; + // add all bytes from utf8buf except the last char + // to the result (last char will be added below) + utf8len--; + c = utf8buf[utf8len]; + for (unsigned x = 0; x < utf8len; x++) { + result.ptr[result.length++] = utf8buf[x]; + } + } else { + // decoding failed, ignore the entire sequence + result.ptr[result.length++] = '\\'; + } + } else { + // TODO: discuss the behavior for unrecognized escape sequences + // most parsers throw an error here - we just ignore it + result.ptr[result.length++] = '\\'; + } + + result.ptr[result.length++] = c; + } else { + if (c == '\\') { + u = true; + } else { + result.ptr[result.length++] = c; + } + } + } + result.ptr[result.length] = 0; + + return result; +} + +static cxmutstr escape_string(cxmutstr str, bool escape_slash) { + // note: this function produces the string without enclosing quotes + // the reason is that we don't want to allocate memory just for that + CxBuffer buf = {0}; + + bool all_printable = true; + for (size_t i = 0; i < str.length; i++) { + unsigned char c = str.ptr[i]; + bool escape = c < 0x20 || c == '\\' || c == '"' + || (escape_slash && c == '/'); + + if (all_printable && escape) { + size_t capa = str.length + 32; + char *space = malloc(capa); + if (space == NULL) return cx_mutstrn(NULL, 0); + cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferWrite(str.ptr, 1, i, &buf); + all_printable = false; + } + if (escape) { + cxBufferPut(&buf, '\\'); + if (c == '\"') { + cxBufferPut(&buf, '\"'); + } else if (c == '\n') { + cxBufferPut(&buf, 'n'); + } else if (c == '\t') { + cxBufferPut(&buf, 't'); + } else if (c == '\r') { + cxBufferPut(&buf, 'r'); + } else if (c == '\\') { + cxBufferPut(&buf, '\\'); + } else if (c == '/') { + cxBufferPut(&buf, '/'); + } else if (c == '\f') { + cxBufferPut(&buf, 'f'); + } else if (c == '\b') { + cxBufferPut(&buf, 'b'); + } else { + char code[6]; + snprintf(code, sizeof(code), "u%04x", (unsigned int) c); + cxBufferPutString(&buf, code); + } + } else if (!all_printable) { + cxBufferPut(&buf, c); + } + } + if (!all_printable) { + str = cx_mutstrn(buf.space, buf.size); + } + cxBufferDestroy(&buf); + return str; +} + +static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { + CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); + if (v == NULL) return NULL; // LCOV_EXCL_LINE + + // initialize the value + v->type = type; + v->allocator = json->allocator; + if (type == CX_JSON_ARRAY) { + cx_array_initialize_a(json->allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE + } else if (type == CX_JSON_OBJECT) { + cx_array_initialize_a(json->allocator, v->value.object.values, 16); + v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); + if (v->value.object.values == NULL || + v->value.object.indices == NULL) + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + + // add the new value to a possible parent + if (json->vbuf_size > 0) { + CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; + assert(parent != NULL); + if (parent->type == CX_JSON_ARRAY) { + CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); + if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } else if (parent->type == CX_JSON_OBJECT) { + // the member was already created after parsing the name + assert(json->uncompleted_member.name.ptr != NULL); + json->uncompleted_member.value = v; + if (json_add_objvalue(parent, json->uncompleted_member)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + } else { + assert(false); // LCOV_EXCL_LINE + } + } + + // add the new value to the stack, if it is an array or object + if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { + CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); + if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } + + // if currently no value is parsed, this is now the value of interest + if (json->parsed == NULL) { + json->parsed = v; + } + + return v; + // LCOV_EXCL_START +create_json_value_exit_error: + cxJsonValueFree(v); + return NULL; + // LCOV_EXCL_STOP +} + +#define JP_STATE_VALUE_BEGIN 0 +#define JP_STATE_VALUE_END 10 +#define JP_STATE_VALUE_BEGIN_OBJ 1 +#define JP_STATE_OBJ_SEP_OR_CLOSE 11 +#define JP_STATE_VALUE_BEGIN_AR 2 +#define JP_STATE_ARRAY_SEP_OR_CLOSE 12 +#define JP_STATE_OBJ_NAME_OR_CLOSE 5 +#define JP_STATE_OBJ_NAME 6 +#define JP_STATE_OBJ_COLON 7 + +void cxJsonInit(CxJson *json, const CxAllocator *allocator) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + memset(json, 0, sizeof(CxJson)); + json->allocator = allocator; + + json->states = json->states_internal; + json->states_capacity = cx_nmemb(json->states_internal); + json->states[0] = JP_STATE_VALUE_BEGIN; + json->states_size = 1; + + json->vbuf = json->vbuf_internal; + json->vbuf_capacity = cx_nmemb(json->vbuf_internal); +} + +void cxJsonDestroy(CxJson *json) { + cxBufferDestroy(&json->buffer); + if (json->states != json->states_internal) { + free(json->states); + } + if (json->vbuf != json->vbuf_internal) { + free(json->vbuf); + } + cxJsonValueFree(json->parsed); + json->parsed = NULL; + if (json->uncompleted_member.name.ptr != NULL) { + cx_strfree_a(json->allocator, &json->uncompleted_member.name); + json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; + } +} + +int cxJsonFilln(CxJson *json, const char *buf, size_t size) { + if (cxBufferEof(&json->buffer)) { + // reinitialize the buffer + cxBufferDestroy(&json->buffer); + cxBufferInit(&json->buffer, (char*) buf, size, + NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); + json->buffer.size = size; + return 0; + } else { + return size != cxBufferAppend(buf, 1, size, &json->buffer); + } +} + +static void json_add_state(CxJson *json, int state) { + // we have guaranteed the necessary space with cx_array_simple_reserve() + // therefore, we can safely add the state in the simplest way possible + json->states[json->states_size++] = state; +} + +#define return_rec(code) \ + token_destroy(&token); \ + return code + +static enum cx_json_status json_parse(CxJson *json) { + // Reserve a pointer for a possibly read value + CxJsonValue *vbuf = NULL; + + // grab the next token + CxJsonToken token; + { + enum cx_json_status ret = token_parse_next(json, &token); + if (ret != CX_JSON_NO_ERROR) { + return ret; + } + } + + // pop the current state + assert(json->states_size > 0); + int state = json->states[--json->states_size]; + + // guarantee that at least two more states fit on the stack + CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); + if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + + + // 0 JP_STATE_VALUE_BEGIN value begin + // 10 JP_STATE_VALUE_END expect value end + // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object) + // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose + // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array) + // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose + // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose + // 6 JP_STATE_OBJ_NAME object, expect name + // 7 JP_STATE_OBJ_COLON object, expect ':' + + if (state < 3) { + // push expected end state to the stack + json_add_state(json, 10 + state); + switch (token.tokentype) { + case CX_JSON_TOKEN_BEGIN_ARRAY: { + if (json_create_value(json, CX_JSON_ARRAY) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_BEGIN_OBJECT: { + if (json_create_value(json, CX_JSON_OBJECT) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_STRING: { + if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + cxmutstr str = unescape_string(json->allocator, token.content); + if (str.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + vbuf->value.string = str; + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_INTEGER: + case CX_JSON_TOKEN_NUMBER: { + int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; + if (NULL == (vbuf = json_create_value(json, type))) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (type == CX_JSON_INTEGER) { + if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } else { + if (cx_strtod(token.content, &vbuf->value.number)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_LITERAL: { + if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { + vbuf->value.literal = CX_JSON_TRUE; + } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { + vbuf->value.literal = CX_JSON_FALSE; + } else { + vbuf->value.literal = CX_JSON_NULL; + } + return_rec(CX_JSON_NO_ERROR); + } + default: { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } + } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) { + // expect ',' or ']' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { + // discard the array from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { + if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + // expect string + if (token.tokentype != CX_JSON_TOKEN_STRING) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + + // add new entry + cxmutstr name = unescape_string(json->allocator, token.content); + if (name.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + assert(json->uncompleted_member.name.ptr == NULL); + json->uncompleted_member.name = name; + assert(json->vbuf_size > 0); + + // next state + json_add_state(json, JP_STATE_OBJ_COLON); + return_rec(CX_JSON_NO_ERROR); + } + } else if (state == JP_STATE_OBJ_COLON) { + // expect ':' + if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + // next state + json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ); + return_rec(CX_JSON_NO_ERROR); + } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) { + // expect ',' or '}' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_OBJ_NAME); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else { + // should be unreachable + assert(false); + return_rec(-1); + } +} + +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { + // check if buffer has been filled + if (json->buffer.space == NULL) { + return CX_JSON_NULL_DATA; + } + + // initialize output value + *value = &cx_json_value_nothing; + + // parse data + CxJsonStatus result; + do { + result = json_parse(json); + if (result == CX_JSON_NO_ERROR && json->states_size == 1) { + // final state reached + assert(json->states[0] == JP_STATE_VALUE_END); + assert(json->vbuf_size == 0); + + // write output value + *value = json->parsed; + json->parsed = NULL; + + // re-initialize state machine + json->states[0] = JP_STATE_VALUE_BEGIN; + + return CX_JSON_NO_ERROR; + } + } while (result == CX_JSON_NO_ERROR); + + // the parser might think there is no data + // but when we did not reach the final state, + // we know that there must be more to come + if (result == CX_JSON_NO_DATA && json->states_size > 1) { + return CX_JSON_INCOMPLETE_DATA; + } + + return result; +} + +void cxJsonValueFree(CxJsonValue *value) { + if (value == NULL || value->type == CX_JSON_NOTHING) return; + switch (value->type) { + case CX_JSON_OBJECT: { + CxJsonObject obj = value->value.object; + for (size_t i = 0; i < obj.values_size; i++) { + cxJsonValueFree(obj.values[i].value); + cx_strfree_a(value->allocator, &obj.values[i].name); + } + cxFree(value->allocator, obj.values); + cxFree(value->allocator, obj.indices); + break; + } + case CX_JSON_ARRAY: { + CxJsonArray array = value->value.array; + for (size_t i = 0; i < array.array_size; i++) { + cxJsonValueFree(array.array[i]); + } + cxFree(value->allocator, array.array); + break; + } + case CX_JSON_STRING: { + cxFree(value->allocator, value->value.string.ptr); + break; + } + default: { + break; + } + } + cxFree(value->allocator, value); +} + +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_OBJECT; + cx_array_initialize_a(allocator, v->value.object.values, 16); + if (v->value.object.values == NULL) { // LCOV_EXCL_START + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); + if (v->value.object.indices == NULL) { // LCOV_EXCL_START + cxFree(allocator, v->value.object.values); + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + return v; +} + +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_ARRAY; + cx_array_initialize_a(allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + return v; +} + +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_NUMBER; + v->value.number = num; + return v; +} + +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_INTEGER; + v->value.integer = num; + return v; +} + +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { + return cxJsonCreateCxString(allocator, cx_str(str)); +} + +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_STRING; + cxmutstr s = cx_strdup_a(allocator, str); + if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } + v->value.string = s; + return v; +} + +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_LITERAL; + v->value.literal = lit; + return v; +} + +// LCOV_EXCL_START +// never called as long as malloc() does not return NULL +static void json_arr_free_temp(CxJsonValue** values, size_t count) { + for (size_t i = 0; i < count; i++) { + if (values[i] == NULL) break; + cxJsonValueFree(values[i]); + } + free(values); +} +// LCOV_EXCL_STOP + +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateNumber(arr->allocator, num[i]); + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateInteger(arr->allocator, num[i]); + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateString(arr->allocator, str[i]); + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateCxString(arr->allocator, str[i]); + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { + CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); + assert(arr->type == CX_JSON_ARRAY); + return cx_array_simple_copy_a(&value_realloc, + arr->value.array.array, + arr->value.array.array_size, + val, count + ); +} + +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { + cxmutstr k = cx_strdup_a(obj->allocator, name); + if (k.ptr == NULL) return -1; + CxJsonObjValue kv = {k, child}; + if (json_add_objvalue(obj, kv)) { + cx_strfree_a(obj->allocator, &k); + return 1; + } else { + return 0; + } +} + +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateObj(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateArr(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { + CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { + CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { + CxJsonValue* v = cxJsonCreateString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { + CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { + CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} + return v; +} + +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return &cx_json_value_nothing; + } + return value->value.array.array[index]; +} + +CxIterator cxJsonArrIter(const CxJsonValue *value) { + return cxIteratorPtr( + value->value.array.array, + value->value.array.array_size + ); +} + +CxIterator cxJsonObjIter(const CxJsonValue *value) { + return cxIterator( + value->value.object.values, + sizeof(CxJsonObjValue), + value->value.object.values_size + ); +} + +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { + CxJsonObjValue *member = json_find_objvalue(value, name); + if (member == NULL) { + return &cx_json_value_nothing; + } else { + return member->value; + } +} + +CxJsonWriter cxJsonWriterCompact(void) { + return (CxJsonWriter) { + false, + true, + 6, + false, + 4, + false + }; +} + +CxJsonWriter cxJsonWriterPretty(bool use_spaces) { + return (CxJsonWriter) { + true, + true, + 6, + use_spaces, + 4, + false + }; +} + +static int cx_json_writer_indent( + void *target, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + if (depth == 0) return 0; + + // determine the width and characters to use + const char* indent; // for 32 prepared chars + size_t width = depth; + if (settings->indent_space) { + if (settings->indent == 0) return 0; + width *= settings->indent; + indent = " "; + } else { + indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + } + + // calculate the number of write calls and write + size_t full = width / 32; + size_t remaining = width % 32; + for (size_t i = 0; i < full; i++) { + if (32 != wfunc(indent, 1, 32, target)) return 1; + } + if (remaining != wfunc(indent, 1, remaining, target)) return 1; + + return 0; +} + + +int cx_json_write_rec( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + // keep track of written items + // the idea is to reduce the number of jumps for error checking + size_t actual = 0, expected = 0; + + // small buffer for number to string conversions + char numbuf[40]; + + // recursively write the values + switch (value->type) { + case CX_JSON_OBJECT: { + const char *begin_obj = "{\n"; + if (settings->pretty) { + actual += wfunc(begin_obj, 1, 2, target); + expected += 2; + } else { + actual += wfunc(begin_obj, 1, 1, target); + expected++; + } + depth++; + size_t elem_count = value->value.object.values_size; + for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { + // get the member either via index array or directly + size_t elem_idx = settings->sort_members + ? look_idx + : value->value.object.indices[look_idx]; + CxJsonObjValue *member = &value->value.object.values[elem_idx]; + if (settings->sort_members) { + depth++;depth--; + } + + // possible indentation + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) { + return 1; // LCOV_EXCL_LINE + } + } + + // the name + actual += wfunc("\"", 1, 1, target); + cxmutstr name = escape_string(member->name, settings->escape_slash); + actual += wfunc(name.ptr, 1, name.length, target); + if (name.ptr != member->name.ptr) { + cx_strfree(&name); + } + actual += wfunc("\"", 1, 1, target); + const char *obj_name_sep = ": "; + if (settings->pretty) { + actual += wfunc(obj_name_sep, 1, 2, target); + expected += 4 + member->name.length; + } else { + actual += wfunc(obj_name_sep, 1, 1, target); + expected += 3 + member->name.length; + } + + // the value + if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; + + // end of object-value + if (look_idx < elem_count - 1) { + const char *obj_value_sep = ",\n"; + if (settings->pretty) { + actual += wfunc(obj_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(obj_value_sep, 1, 1, target); + expected++; + } + } else { + if (settings->pretty) { + actual += wfunc("\n", 1, 1, target); + expected ++; + } + } + } + depth--; + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1; + } + actual += wfunc("}", 1, 1, target); + expected++; + break; + } + case CX_JSON_ARRAY: { + actual += wfunc("[", 1, 1, target); + expected++; + CxIterator iter = cxJsonArrIter(value); + cx_foreach(CxJsonValue*, element, iter) { + if (cx_json_write_rec( + target, element, + wfunc, settings, depth) + ) return 1; + + if (iter.index < iter.elem_count - 1) { + const char *arr_value_sep = ", "; + if (settings->pretty) { + actual += wfunc(arr_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(arr_value_sep, 1, 1, target); + expected++; + } + } + } + actual += wfunc("]", 1, 1, target); + expected++; + break; + } + case CX_JSON_STRING: { + actual += wfunc("\"", 1, 1, target); + cxmutstr str = escape_string(value->value.string, settings->escape_slash); + actual += wfunc(str.ptr, 1, str.length, target); + if (str.ptr != value->value.string.ptr) { + cx_strfree(&str); + } + actual += wfunc("\"", 1, 1, target); + expected += 2 + value->value.string.length; + break; + } + case CX_JSON_NUMBER: { + int precision = settings->frac_max_digits; + // because of the way how %g is defined, we need to + // double the precision and truncate ourselves + precision = 1 + (precision > 15 ? 30 : 2 * precision); + snprintf(numbuf, 40, "%.*g", precision, value->value.number); + char *dot, *exp; + unsigned char max_digits; + // find the decimal separator and hope that it's one of . or , + dot = strchr(numbuf, '.'); + if (dot == NULL) { + dot = strchr(numbuf, ','); + } + if (dot == NULL) { + // no decimal separator found + // output everything until a possible exponent + max_digits = 30; + dot = numbuf; + } else { + // found a decimal separator + // output everything until the separator + // and set max digits to what the settings say + size_t len = dot - numbuf; + actual += wfunc(numbuf, 1, len, target); + expected += len; + max_digits = settings->frac_max_digits; + if (max_digits > 15) { + max_digits = 15; + } + // locale independent separator + if (max_digits > 0) { + actual += wfunc(".", 1, 1, target); + expected++; + } + dot++; + } + // find the exponent + exp = strchr(dot, 'e'); + if (exp == NULL) { + // no exponent - output the rest + if (max_digits > 0) { + size_t len = strlen(dot); + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + } else { + // exponent found - truncate the frac digits + // and then output the rest + if (max_digits > 0) { + size_t len = exp - dot - 1; + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + actual += wfunc("e", 1, 1, target); + expected++; + exp++; + size_t len = strlen(exp); + actual += wfunc(exp, 1, len, target); + expected += len; + } + break; + } + case CX_JSON_INTEGER: { + snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 1, len, target); + expected += len; + break; + } + case CX_JSON_LITERAL: { + if (value->value.literal == CX_JSON_TRUE) { + actual += wfunc("true", 1, 4, target); + expected += 4; + } else if (value->value.literal == CX_JSON_FALSE) { + actual += wfunc("false", 1, 5, target); + expected += 5; + } else { + actual += wfunc("null", 1, 4, target); + expected += 4; + } + break; + } + case CX_JSON_NOTHING: { + // deliberately supported as an empty string! + // users might want to just write the result + // of a get operation without testing the value + // and therefore this should not blow up + break; + } + default: assert(false); // LCOV_EXCL_LINE + } + + return expected != actual; +} + +int cxJsonWrite( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings +) { + assert(target != NULL); + assert(value != NULL); + assert(wfunc != NULL); + + CxJsonWriter writer_default = cxJsonWriterCompact(); + if (settings == NULL) { + settings = &writer_default; + } + return cx_json_write_rec(target, value, wfunc, settings, 0); +}
--- a/src/ucx/linked_list.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/linked_list.c Sun Mar 02 18:10:52 2025 +0100 @@ -27,7 +27,7 @@ */ #include "cx/linked_list.h" -#include "cx/utils.h" +#include "cx/compare.h" #include <string.h> #include <assert.h> @@ -40,7 +40,7 @@ #define ll_data(node) (((char*)(node))+loc_data) void *cx_linked_list_at( - void const *start, + const void *start, size_t start_index, ptrdiff_t loc_advance, size_t index @@ -48,7 +48,7 @@ assert(start != NULL); assert(loc_advance >= 0); size_t i = start_index; - void const *cur = start; + const void *cur = start; while (i != index && cur != NULL) { cur = ll_advance(cur); i < index ? i++ : i--; @@ -56,47 +56,51 @@ return (void *) cur; } -ssize_t cx_linked_list_find( - void const *start, +void *cx_linked_list_find( + const void *start, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func, - void const *elem + const void *elem, + size_t *found_index ) { assert(start != NULL); assert(loc_advance >= 0); assert(loc_data >= 0); assert(cmp_func); - void const *node = start; - ssize_t index = 0; + void *node = (void*) start; + size_t index = 0; do { void *current = ll_data(node); if (cmp_func(current, elem) == 0) { - return index; + if (found_index != NULL) { + *found_index = index; + } + return node; } node = ll_advance(node); index++; } while (node != NULL); - return -1; + return NULL; } void *cx_linked_list_first( - void const *node, + const void *node, ptrdiff_t loc_prev ) { return cx_linked_list_last(node, loc_prev); } void *cx_linked_list_last( - void const *node, + const void *node, ptrdiff_t loc_next ) { assert(node != NULL); assert(loc_next >= 0); - void const *cur = node; - void const *last; + const void *cur = node; + const void *last; do { last = cur; } while ((cur = ll_next(cur)) != NULL); @@ -105,16 +109,16 @@ } void *cx_linked_list_prev( - void const *begin, + const void *begin, ptrdiff_t loc_next, - void const *node + const void *node ) { assert(begin != NULL); assert(node != NULL); assert(loc_next >= 0); if (begin == node) return NULL; - void const *cur = begin; - void const *next; + const void *cur = begin; + const void *next; while (1) { next = ll_next(cur); if (next == node) return (void *) cur; @@ -227,19 +231,111 @@ } } -void cx_linked_list_remove( +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + cx_linked_list_insert_sorted_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} + +void cx_linked_list_insert_sorted_chain( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, - void *node + void *insert_begin, + cx_compare_func cmp_func +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(insert_begin != NULL); + + // track currently observed nodes + void *dest_prev = NULL; + void *dest = *begin; + void *src = insert_begin; + + // special case: list is empty + if (dest == NULL) { + *begin = src; + if (end != NULL) { + *end = cx_linked_list_last(src, loc_next); + } + return; + } + + // search the list for insertion points + while (dest != NULL && src != NULL) { + // compare current list node with source node + // if less or equal, skip + if (cmp_func(dest, src) <= 0) { + dest_prev = dest; + dest = ll_next(dest); + continue; + } + + // determine chain of elements that can be inserted + void *end_of_chain = src; + void *next_in_chain = ll_next(src); + while (next_in_chain != NULL) { + // once we become larger than the list elem, break + if (cmp_func(dest, next_in_chain) <= 0) { + break; + } + // otherwise, we can insert one more + end_of_chain = next_in_chain; + next_in_chain = ll_next(next_in_chain); + } + + // insert the elements + if (dest_prev == NULL) { + // new begin + *begin = src; + } else { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next); + + // continue with next + src = next_in_chain; + dest_prev = dest; + dest = ll_next(dest); + } + + // insert remaining items + if (src != NULL) { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + + // determine new end of list, if requested + if (end != NULL) { + *end = cx_linked_list_last( + dest != NULL ? dest : dest_prev, loc_next); + } +} + +size_t cx_linked_list_remove_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + size_t num ) { assert(node != NULL); assert(loc_next >= 0); assert(loc_prev >= 0 || begin != NULL); + // easy exit + if (num == 0) return 0; + // find adjacent nodes - void *next = ll_next(node); void *prev; if (loc_prev >= 0) { prev = ll_prev(node); @@ -247,6 +343,12 @@ prev = cx_linked_list_prev(*begin, loc_next, node); } + void *next = ll_next(node); + size_t removed = 1; + for (; removed < num && next != NULL ; removed++) { + next = ll_next(next); + } + // update next pointer of prev node, or set begin if (prev == NULL) { if (begin != NULL) { @@ -264,10 +366,12 @@ } else if (loc_prev >= 0) { ll_prev(next) = prev; } + + return removed; } size_t cx_linked_list_size( - void const *node, + const void *node, ptrdiff_t loc_next ) { assert(loc_next >= 0); @@ -327,13 +431,13 @@ // Update pointer if (loc_prev >= 0) ll_prev(sorted[0]) = NULL; - cx_for_n (i, length - 1) { + for (size_t i = 0 ; i < length - 1; i++) { cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next); } ll_next(sorted[length - 1]) = NULL; *begin = sorted[0]; - *end = sorted[length-1]; + *end = sorted[length - 1]; if (sorted != sbo) { free(sorted); } @@ -405,17 +509,17 @@ } int cx_linked_list_compare( - void const *begin_left, - void const *begin_right, + const void *begin_left, + const void *begin_right, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func ) { - void const *left = begin_left, *right = begin_right; + const void *left = begin_left, *right = begin_right; while (left != NULL && right != NULL) { - void const *left_data = ll_data(left); - void const *right_data = ll_data(right); + const void *left_data = ll_data(left); + const void *right_data = ll_data(right); int result = cmp_func(left_data, right_data); if (result != 0) return result; left = ll_advance(left); @@ -460,8 +564,6 @@ // HIGH LEVEL LINKED LIST IMPLEMENTATION -bool CX_DISABLE_LINKED_LIST_SWAP_SBO = false; - typedef struct cx_linked_list_node cx_linked_list_node; struct cx_linked_list_node { cx_linked_list_node *prev; @@ -480,34 +582,38 @@ } cx_linked_list; static cx_linked_list_node *cx_ll_node_at( - cx_linked_list const *list, + const cx_linked_list *list, size_t index ) { - if (index >= list->base.size) { + if (index >= list->base.collection.size) { return NULL; - } else if (index > list->base.size / 2) { - return cx_linked_list_at(list->end, list->base.size - 1, CX_LL_LOC_PREV, index); + } else if (index > list->base.collection.size / 2) { + return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index); } else { return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index); } } +static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) { + return cxMalloc(list->collection.allocator, + sizeof(cx_linked_list_node) + list->collection.elem_size); +} + static int cx_ll_insert_at( struct cx_list_s *list, cx_linked_list_node *node, - void const *elem + const void *elem ) { // create the new new_node - cx_linked_list_node *new_node = cxMalloc(list->allocator, - sizeof(cx_linked_list_node) + list->item_size); + cx_linked_list_node *new_node = cx_ll_malloc_node(list); // sortir if failed if (new_node == NULL) return 1; // initialize new new_node new_node->prev = new_node->next = NULL; - memcpy(new_node->payload, elem, list->item_size); + memcpy(new_node->payload, elem, list->collection.elem_size); // insert cx_linked_list *ll = (cx_linked_list *) list; @@ -518,26 +624,24 @@ ); // increase the size and return - list->size++; + list->collection.size++; return 0; } static size_t cx_ll_insert_array( struct cx_list_s *list, size_t index, - void const *array, + const void *array, size_t n ) { // out-of bounds and corner case check - if (index > list->size || n == 0) return 0; + if (index > list->collection.size || n == 0) return 0; // find position efficiently cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert - if (0 != cx_ll_insert_at(list, node, array)) { - return 1; - } + if (0 != cx_ll_insert_at(list, node, array)) return 1; // is there more? if (n == 1) return 1; @@ -545,13 +649,11 @@ // we now know exactly where we are node = node == NULL ? ((cx_linked_list *) list)->begin : node->next; - // we can add the remaining nodes and immedately advance to the inserted node - char const *source = array; + // we can add the remaining nodes and immediately advance to the inserted node + const char *source = array; for (size_t i = 1; i < n; i++) { - source += list->item_size; - if (0 != cx_ll_insert_at(list, node, source)) { - return i; - } + source += list->collection.elem_size; + if (0 != cx_ll_insert_at(list, node, source)) return i; node = node->next; } return n; @@ -560,67 +662,151 @@ static int cx_ll_insert_element( struct cx_list_s *list, size_t index, - void const *element + const void *element ) { return 1 != cx_ll_insert_array(list, index, element, 1); } -static int cx_ll_remove( +static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; + +static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { + const cx_linked_list_node *left = l; + const cx_linked_list_node *right = r; + return cx_ll_insert_sorted_cmp_func(left->payload, right->payload); +} + +static size_t cx_ll_insert_sorted( struct cx_list_s *list, - size_t index + const void *array, + size_t n +) { + // special case + if (n == 0) return 0; + + // create a new chain of nodes + cx_linked_list_node *chain = cx_ll_malloc_node(list); + if (chain == NULL) return 0; + + memcpy(chain->payload, array, list->collection.elem_size); + chain->prev = NULL; + chain->next = NULL; + + // add all elements from the array to that chain + cx_linked_list_node *prev = chain; + const char *src = array; + size_t inserted = 1; + for (; inserted < n; inserted++) { + cx_linked_list_node *next = cx_ll_malloc_node(list); + if (next == NULL) break; + src += list->collection.elem_size; + memcpy(next->payload, src, list->collection.elem_size); + prev->next = next; + next->prev = prev; + prev = next; + } + prev->next = NULL; + + // invoke the low level function + cx_linked_list *ll = (cx_linked_list *) list; + cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; + cx_linked_list_insert_sorted_chain( + (void **) &ll->begin, + (void **) &ll->end, + CX_LL_LOC_PREV, + CX_LL_LOC_NEXT, + chain, + cx_ll_insert_sorted_cmp_helper + ); + + // adjust the list metadata + list->collection.size += inserted; + + return inserted; +} + +static size_t cx_ll_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf ) { cx_linked_list *ll = (cx_linked_list *) list; cx_linked_list_node *node = cx_ll_node_at(ll, index); // out-of-bounds check - if (node == NULL) return 1; - - // element destruction - cx_invoke_destructor(list, node->payload); + if (node == NULL) return 0; // remove - cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + size_t removed = cx_linked_list_remove_chain( + (void **) &ll->begin, + (void **) &ll->end, + CX_LL_LOC_PREV, + CX_LL_LOC_NEXT, + node, + num + ); // adjust size - list->size--; + list->collection.size -= removed; + + // copy or destroy the removed chain + if (targetbuf == NULL) { + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // element destruction + cx_invoke_destructor(list, n->payload); - // free and return - cxFree(list->allocator, node); + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } else { + char *dest = targetbuf; + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // copy payload + memcpy(dest, n->payload, list->collection.elem_size); - return 0; + // advance target buffer + dest += list->collection.elem_size; + + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } + + return removed; } static void cx_ll_clear(struct cx_list_s *list) { - if (list->size == 0) return; + if (list->collection.size == 0) return; cx_linked_list *ll = (cx_linked_list *) list; cx_linked_list_node *node = ll->begin; while (node != NULL) { cx_invoke_destructor(list, node->payload); cx_linked_list_node *next = node->next; - cxFree(list->allocator, node); + cxFree(list->collection.allocator, node); node = next; } ll->begin = ll->end = NULL; - list->size = 0; + list->collection.size = 0; } -#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE -#define CX_LINKED_LIST_SWAP_SBO_SIZE 128 -#endif - static int cx_ll_swap( struct cx_list_s *list, size_t i, size_t j ) { - if (i >= list->size || j >= list->size) return 1; + if (i >= list->collection.size || j >= list->collection.size) return 1; if (i == j) return 0; // perform an optimized search that finds both elements in one run cx_linked_list *ll = (cx_linked_list *) list; - size_t mid = list->size / 2; + size_t mid = list->collection.size / 2; size_t left, right; if (i < j) { left = i; @@ -629,10 +815,11 @@ left = j; right = i; } - cx_linked_list_node *nleft, *nright; + cx_linked_list_node *nleft = NULL, *nright = NULL; if (left < mid && right < mid) { // case 1: both items left from mid nleft = cx_ll_node_at(ll, left); + assert(nleft != NULL); nright = nleft; for (size_t c = left; c < right; c++) { nright = nright->next; @@ -640,6 +827,7 @@ } else if (left >= mid && right >= mid) { // case 2: both items right from mid nright = cx_ll_node_at(ll, right); + assert(nright != NULL); nleft = nright; for (size_t c = right; c > left; c--) { nleft = nleft->prev; @@ -650,7 +838,7 @@ // chose the closest to begin / end size_t closest; size_t other; - size_t diff2boundary = list->size - right - 1; + size_t diff2boundary = list->collection.size - right - 1; if (left <= diff2boundary) { closest = left; other = right; @@ -686,48 +874,40 @@ } } - if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE || CX_DISABLE_LINKED_LIST_SWAP_SBO) { - cx_linked_list_node *prev = nleft->prev; - cx_linked_list_node *next = nright->next; - cx_linked_list_node *midstart = nleft->next; - cx_linked_list_node *midend = nright->prev; + cx_linked_list_node *prev = nleft->prev; + cx_linked_list_node *next = nright->next; + cx_linked_list_node *midstart = nleft->next; + cx_linked_list_node *midend = nright->prev; - if (prev == NULL) { - ll->begin = nright; - } else { - prev->next = nright; - } - nright->prev = prev; - if (midstart == nright) { - // special case: both nodes are adjacent - nright->next = nleft; - nleft->prev = nright; - } else { - // likely case: a chain is between the two nodes - nright->next = midstart; - midstart->prev = nright; - midend->next = nleft; - nleft->prev = midend; - } - nleft->next = next; - if (next == NULL) { - ll->end = nleft; - } else { - next->prev = nleft; - } + if (prev == NULL) { + ll->begin = nright; + } else { + prev->next = nright; + } + nright->prev = prev; + if (midstart == nright) { + // special case: both nodes are adjacent + nright->next = nleft; + nleft->prev = nright; } else { - // swap payloads to avoid relinking - char buf[CX_LINKED_LIST_SWAP_SBO_SIZE]; - memcpy(buf, nleft->payload, list->item_size); - memcpy(nleft->payload, nright->payload, list->item_size); - memcpy(nright->payload, buf, list->item_size); + // likely case: a chain is between the two nodes + nright->next = midstart; + midstart->prev = nright; + midend->next = nleft; + nleft->prev = midend; + } + nleft->next = next; + if (next == NULL) { + ll->end = nleft; + } else { + next->prev = nleft; } return 0; } static void *cx_ll_at( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index ) { cx_linked_list *ll = (cx_linked_list *) list; @@ -735,20 +915,39 @@ return node == NULL ? NULL : node->payload; } -static ssize_t cx_ll_find( - struct cx_list_s const *list, - void const *elem +static size_t cx_ll_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove ) { - return cx_linked_list_find(((cx_linked_list *) list)->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, - list->cmpfunc, elem); + if (list->collection.size == 0) return 0; + + size_t index; + cx_linked_list *ll = ((cx_linked_list *) list); + cx_linked_list_node *node = cx_linked_list_find( + ll->begin, + CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc, elem, + &index + ); + if (node == NULL) { + return list->collection.size; + } + if (remove) { + cx_invoke_destructor(list, node->payload); + cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + list->collection.size--; + cxFree(list->collection.allocator, node); + } + return index; } static void cx_ll_sort(struct cx_list_s *list) { cx_linked_list *ll = (cx_linked_list *) list; cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA, - list->cmpfunc); + list->collection.cmpfunc); } static void cx_ll_reverse(struct cx_list_s *list) { @@ -757,37 +956,35 @@ } static int cx_ll_compare( - struct cx_list_s const *list, - struct cx_list_s const *other + const struct cx_list_s *list, + const struct cx_list_s *other ) { cx_linked_list *left = (cx_linked_list *) list; cx_linked_list *right = (cx_linked_list *) other; return cx_linked_list_compare(left->begin, right->begin, CX_LL_LOC_NEXT, CX_LL_LOC_DATA, - list->cmpfunc); + list->collection.cmpfunc); } -static bool cx_ll_iter_valid(void const *it) { - struct cx_iterator_s const *iter = it; +static bool cx_ll_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; return iter->elem_handle != NULL; } static void cx_ll_iter_next(void *it) { - struct cx_iterator_base_s *itbase = it; - if (itbase->remove) { - itbase->remove = false; - struct cx_mut_iterator_s *iter = it; - struct cx_list_s *list = iter->src_handle; - cx_linked_list *ll = iter->src_handle; + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + struct cx_list_s *list = iter->src_handle.m; + cx_linked_list *ll = iter->src_handle.m; cx_linked_list_node *node = iter->elem_handle; iter->elem_handle = node->next; cx_invoke_destructor(list, node->payload); cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); - list->size--; - cxFree(list->allocator, node); + list->collection.size--; + cxFree(list->collection.allocator, node); } else { - struct cx_iterator_s *iter = it; iter->index++; cx_linked_list_node *node = iter->elem_handle; iter->elem_handle = node->next; @@ -795,78 +992,75 @@ } static void cx_ll_iter_prev(void *it) { - struct cx_iterator_base_s *itbase = it; - if (itbase->remove) { - itbase->remove = false; - struct cx_mut_iterator_s *iter = it; - struct cx_list_s *list = iter->src_handle; - cx_linked_list *ll = iter->src_handle; + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + struct cx_list_s *list = iter->src_handle.m; + cx_linked_list *ll = iter->src_handle.m; cx_linked_list_node *node = iter->elem_handle; iter->elem_handle = node->prev; iter->index--; cx_invoke_destructor(list, node->payload); cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); - list->size--; - cxFree(list->allocator, node); + list->collection.size--; + cxFree(list->collection.allocator, node); } else { - struct cx_iterator_s *iter = it; iter->index--; cx_linked_list_node *node = iter->elem_handle; iter->elem_handle = node->prev; } } -static void *cx_ll_iter_current(void const *it) { - struct cx_iterator_s const *iter = it; +static void *cx_ll_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; cx_linked_list_node *node = iter->elem_handle; return node->payload; } -static bool cx_ll_iter_flag_rm(void *it) { - struct cx_iterator_base_s *iter = it; - if (iter->mutating) { - iter->remove = true; - return true; - } else { - return false; - } -} - static CxIterator cx_ll_iterator( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index, bool backwards ) { CxIterator iter; iter.index = index; - iter.src_handle = list; - iter.elem_handle = cx_ll_node_at((cx_linked_list const *) list, index); + iter.src_handle.c = list; + iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index); + iter.elem_size = list->collection.elem_size; + iter.elem_count = list->collection.size; iter.base.valid = cx_ll_iter_valid; iter.base.current = cx_ll_iter_current; iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next; - iter.base.flag_removal = cx_ll_iter_flag_rm; iter.base.mutating = false; iter.base.remove = false; return iter; } static int cx_ll_insert_iter( - CxMutIterator *iter, - void const *elem, + CxIterator *iter, + const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle; + struct cx_list_s *list = iter->src_handle.m; cx_linked_list_node *node = iter->elem_handle; if (node != NULL) { assert(prepend >= 0 && prepend <= 1); cx_linked_list_node *choice[2] = {node, node->prev}; int result = cx_ll_insert_at(list, choice[prepend], elem); - iter->index += prepend * (0 == result); + if (result == 0) { + iter->elem_count++; + if (prepend) { + iter->index++; + } + } return result; } else { - int result = cx_ll_insert_element(list, list->size, elem); - iter->index = list->size; + int result = cx_ll_insert_element(list, list->collection.size, elem); + if (result == 0) { + iter->elem_count++; + iter->index = list->collection.size; + } return result; } } @@ -878,23 +1072,24 @@ while (node) { cx_invoke_destructor(list, node->payload); void *next = node->next; - cxFree(list->allocator, node); + cxFree(list->collection.allocator, node); node = next; } - cxFree(list->allocator, list); + cxFree(list->collection.allocator, list); } static cx_list_class cx_linked_list_class = { cx_ll_destructor, cx_ll_insert_element, cx_ll_insert_array, + cx_ll_insert_sorted, cx_ll_insert_iter, cx_ll_remove, cx_ll_clear, cx_ll_swap, cx_ll_at, - cx_ll_find, + cx_ll_find_remove, cx_ll_sort, cx_ll_compare, cx_ll_reverse, @@ -902,9 +1097,9 @@ }; CxList *cxLinkedListCreate( - CxAllocator const *allocator, + const CxAllocator *allocator, cx_compare_func comparator, - size_t item_size + size_t elem_size ) { if (allocator == NULL) { allocator = cxDefaultAllocator; @@ -912,16 +1107,8 @@ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; - - list->base.cl = &cx_linked_list_class; - list->base.allocator = allocator; - list->base.cmpfunc = comparator; - - if (item_size > 0) { - list->base.item_size = item_size; - } else { - cxListStorePointers((CxList *) list); - } + cx_list_init((CxList*)list, &cx_linked_list_class, + allocator, comparator, elem_size); return (CxList *) list; }
--- a/src/ucx/list.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/list.c Sun Mar 02 18:10:52 2025 +0100 @@ -35,37 +35,37 @@ static _Thread_local cx_compare_func cx_pl_cmpfunc_impl; static int cx_pl_cmpfunc( - void const *l, - void const *r + const void *l, + const void *r ) { void *const *lptr = l; void *const *rptr = r; - void const *left = lptr == NULL ? NULL : *lptr; - void const *right = rptr == NULL ? NULL : *rptr; + const void *left = lptr == NULL ? NULL : *lptr; + const void *right = rptr == NULL ? NULL : *rptr; return cx_pl_cmpfunc_impl(left, right); } -static void cx_pl_hack_cmpfunc(struct cx_list_s const *list) { +static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) { // cast away const - this is the hacky thing - struct cx_list_s *l = (struct cx_list_s *) list; + struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; cx_pl_cmpfunc_impl = l->cmpfunc; l->cmpfunc = cx_pl_cmpfunc; } -static void cx_pl_unhack_cmpfunc(struct cx_list_s const *list) { +static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) { // cast away const - this is the hacky thing - struct cx_list_s *l = (struct cx_list_s *) list; + struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; l->cmpfunc = cx_pl_cmpfunc_impl; } static void cx_pl_destructor(struct cx_list_s *list) { - list->climpl->destructor(list); + list->climpl->deallocate(list); } static int cx_pl_insert_element( struct cx_list_s *list, size_t index, - void const *element + const void *element ) { return list->climpl->insert_element(list, index, &element); } @@ -73,26 +73,39 @@ static size_t cx_pl_insert_array( struct cx_list_s *list, size_t index, - void const *array, + const void *array, size_t n ) { return list->climpl->insert_array(list, index, array, n); } +static size_t cx_pl_insert_sorted( + struct cx_list_s *list, + const void *array, + size_t n +) { + cx_pl_hack_cmpfunc(list); + size_t result = list->climpl->insert_sorted(list, array, n); + cx_pl_unhack_cmpfunc(list); + return result; +} + static int cx_pl_insert_iter( - struct cx_mut_iterator_s *iter, - void const *elem, + struct cx_iterator_s *iter, + const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle; + struct cx_list_s *list = iter->src_handle.m; return list->climpl->insert_iter(iter, &elem, prepend); } -static int cx_pl_remove( +static size_t cx_pl_remove( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ) { - return list->climpl->remove(list, index); + return list->climpl->remove(list, index, num, targetbuf); } static void cx_pl_clear(struct cx_list_s *list) { @@ -108,19 +121,20 @@ } static void *cx_pl_at( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index ) { void **ptr = list->climpl->at(list, index); return ptr == NULL ? NULL : *ptr; } -static ssize_t cx_pl_find( - struct cx_list_s const *list, - void const *elem +static size_t cx_pl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove ) { cx_pl_hack_cmpfunc(list); - ssize_t ret = list->climpl->find(list, &elem); + size_t ret = list->climpl->find_remove(list, &elem, remove); cx_pl_unhack_cmpfunc(list); return ret; } @@ -132,8 +146,8 @@ } static int cx_pl_compare( - struct cx_list_s const *list, - struct cx_list_s const *other + const struct cx_list_s *list, + const struct cx_list_s *other ) { cx_pl_hack_cmpfunc(list); int ret = list->climpl->compare(list, other); @@ -145,14 +159,14 @@ list->climpl->reverse(list); } -static void *cx_pl_iter_current(void const *it) { - struct cx_iterator_s const *iter = it; +static void *cx_pl_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; void **ptr = iter->base.current_impl(it); return ptr == NULL ? NULL : *ptr; } static struct cx_iterator_s cx_pl_iterator( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index, bool backwards ) { @@ -166,74 +180,52 @@ cx_pl_destructor, cx_pl_insert_element, cx_pl_insert_array, + cx_pl_insert_sorted, cx_pl_insert_iter, cx_pl_remove, cx_pl_clear, cx_pl_swap, cx_pl_at, - cx_pl_find, + cx_pl_find_remove, cx_pl_sort, cx_pl_compare, cx_pl_reverse, cx_pl_iterator, }; - -void cxListStoreObjects(CxList *list) { - list->store_pointer = false; - if (list->climpl != NULL) { - list->cl = list->climpl; - list->climpl = NULL; - } -} - -void cxListStorePointers(CxList *list) { - list->item_size = sizeof(void *); - list->store_pointer = true; - list->climpl = list->cl; - list->cl = &cx_pointer_list_class; -} - // </editor-fold> // <editor-fold desc="empty list implementation"> -static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) { +static void cx_emptyl_noop(cx_attr_unused CxList *list) { // this is a noop, but MUST be implemented } static void *cx_emptyl_at( - __attribute__((__unused__)) struct cx_list_s const *list, - __attribute__((__unused__)) size_t index + cx_attr_unused const struct cx_list_s *list, + cx_attr_unused size_t index ) { return NULL; } -static ssize_t cx_emptyl_find( - __attribute__((__unused__)) struct cx_list_s const *list, - __attribute__((__unused__)) void const *elem +static size_t cx_emptyl_find_remove( + cx_attr_unused struct cx_list_s *list, + cx_attr_unused const void *elem, + cx_attr_unused bool remove ) { - return -1; + return 0; } -static int cx_emptyl_compare( - __attribute__((__unused__)) struct cx_list_s const *list, - struct cx_list_s const *other -) { - if (other->size == 0) return 0; - return -1; -} - -static bool cx_emptyl_iter_valid(__attribute__((__unused__)) void const *iter) { +static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_emptyl_iterator( - struct cx_list_s const *list, + const struct cx_list_s *list, size_t index, - __attribute__((__unused__)) bool backwards + cx_attr_unused bool backwards ) { CxIterator iter = {0}; - iter.src_handle = list; + iter.src_handle.c = list; iter.index = index; iter.base.valid = cx_emptyl_iter_valid; return iter; @@ -245,17 +237,19 @@ NULL, NULL, NULL, + NULL, cx_emptyl_noop, NULL, cx_emptyl_at, - cx_emptyl_find, + cx_emptyl_find_remove, cx_emptyl_noop, - cx_emptyl_compare, + NULL, cx_emptyl_noop, cx_emptyl_iterator, }; CxList cx_empty_list = { + { NULL, NULL, 0, @@ -264,41 +258,204 @@ NULL, NULL, false, - &cx_empty_list_class, - NULL + true, + }, + &cx_empty_list_class, + NULL }; CxList *const cxEmptyList = &cx_empty_list; // </editor-fold> -void cxListDestroy(CxList *list) { - list->cl->destructor(list); +#define invoke_list_func(name, list, ...) \ + ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \ + (list, __VA_ARGS__) + +size_t cx_list_default_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +) { + size_t elem_size = list->collection.elem_size; + const char *src = data; + size_t i = 0; + for (; i < n; i++) { + if (0 != invoke_list_func( + insert_element, list, index + i, + src + (i * elem_size))) return i; + } + return i; +} + +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // corner case + if (n == 0) return 0; + + size_t elem_size = list->collection.elem_size; + cx_compare_func cmp = list->collection.cmpfunc; + const char *src = sorted_data; + + // track indices and number of inserted items + size_t di = 0, si = 0, inserted = 0; + + // search the list for insertion points + for (; di < list->collection.size; di++) { + const void *list_elm = invoke_list_func(at, list, di); + + // compare current list element with first source element + // if less or equal, skip + if (cmp(list_elm, src) <= 0) { + continue; + } + + // determine number of consecutive elements that can be inserted + size_t ins = 1; + const char *next = src; + while (++si < n) { + next += elem_size; + // once we become larger than the list elem, break + if (cmp(list_elm, next) <= 0) { + break; + } + // otherwise, we can insert one more + ins++; + } + + // insert the elements at location si + if (ins == 1) { + if (0 != invoke_list_func( + insert_element, list, di, src)) return inserted; + } else { + size_t r = invoke_list_func(insert_array, list, di, src, ins); + if (r < ins) return inserted + r; + } + inserted += ins; + di += ins; + + // everything inserted? + if (inserted == n) return inserted; + src = next; + } + + // insert remaining items + if (si < n) { + inserted += invoke_list_func(insert_array, list, di, src, n - si); + } + + return inserted; +} + +void cx_list_default_sort(struct cx_list_s *list) { + size_t elem_size = list->collection.elem_size; + size_t list_size = list->collection.size; + void *tmp = malloc(elem_size * list_size); + if (tmp == NULL) abort(); + + // copy elements from source array + char *loc = tmp; + for (size_t i = 0; i < list_size; i++) { + void *src = invoke_list_func(at, list, i); + memcpy(loc, src, elem_size); + loc += elem_size; + } + + // qsort + qsort(tmp, list_size, elem_size, + list->collection.cmpfunc); + + // copy elements back + loc = tmp; + for (size_t i = 0; i < list_size; i++) { + void *dest = invoke_list_func(at, list, i); + memcpy(dest, loc, elem_size); + loc += elem_size; + } + + free(tmp); +} + +int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { + if (i == j) return 0; + if (i >= list->collection.size) return 1; + if (j >= list->collection.size) return 1; + + size_t elem_size = list->collection.elem_size; + + void *tmp = malloc(elem_size); + if (tmp == NULL) return 1; + + void *ip = invoke_list_func(at, list, i); + void *jp = invoke_list_func(at, list, j); + + memcpy(tmp, ip, elem_size); + memcpy(ip, jp, elem_size); + memcpy(jp, tmp, elem_size); + + free(tmp); + + return 0; +} + +void cx_list_init( + struct cx_list_s *list, + struct cx_list_class_s *cl, + const struct cx_allocator_s *allocator, + cx_compare_func comparator, + size_t elem_size +) { + list->cl = cl; + list->collection.allocator = allocator; + list->collection.cmpfunc = comparator; + if (elem_size > 0) { + list->collection.elem_size = elem_size; + } else { + list->collection.elem_size = sizeof(void *); + if (list->collection.cmpfunc == NULL) { + list->collection.cmpfunc = cx_cmp_ptr; + } + list->collection.store_pointer = true; + list->climpl = list->cl; + list->cl = &cx_pointer_list_class; + } } int cxListCompare( - CxList const *list, - CxList const *other + const CxList *list, + const CxList *other ) { - if ( - // if one is storing pointers but the other is not - (list->store_pointer ^ other->store_pointer) || + bool cannot_optimize = false; - // if one class is wrapped but the other is not - ((list->climpl == NULL) ^ (other->climpl == NULL)) || + // if one is storing pointers but the other is not + cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer; + + // if one class is wrapped but the other is not + cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL); - // if the resolved compare functions are not the same - ((list->climpl != NULL ? list->climpl->compare : list->cl->compare) != - (other->climpl != NULL ? other->climpl->compare : other->cl->compare)) - ) { + // if the compare functions do not match or both are NULL + if (!cannot_optimize) { + cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ? + list->climpl->compare : list->cl->compare); + cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ? + other->climpl->compare : other->cl->compare); + cannot_optimize |= list_cmp != other_cmp; + cannot_optimize |= list_cmp == NULL; + } + + if (cannot_optimize) { // lists are definitely different - cannot use internal compare function - if (list->size == other->size) { - CxIterator left = cxListIterator(list); - CxIterator right = cxListIterator(other); - for (size_t i = 0; i < list->size; i++) { + if (list->collection.size == other->collection.size) { + CxIterator left = list->cl->iterator(list, 0, false); + CxIterator right = other->cl->iterator(other, 0, false); + for (size_t i = 0; i < list->collection.size; i++) { void *leftValue = cxIteratorCurrent(left); void *rightValue = cxIteratorCurrent(right); - int d = list->cmpfunc(leftValue, rightValue); + int d = list->collection.cmpfunc(leftValue, rightValue); if (d != 0) { return d; } @@ -307,7 +464,7 @@ } return 0; } else { - return list->size < other->size ? -1 : 1; + return list->collection.size < other->collection.size ? -1 : 1; } } else { // lists are compatible @@ -315,28 +472,25 @@ } } -CxMutIterator cxListMutIteratorAt( +CxIterator cxListMutIteratorAt( CxList *list, size_t index ) { CxIterator it = list->cl->iterator(list, index, false); it.base.mutating = true; - - // we know the iterators share the same memory layout - CxMutIterator iter; - memcpy(&iter, &it, sizeof(CxMutIterator)); - return iter; + return it; } -CxMutIterator cxListMutBackwardsIteratorAt( +CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index ) { CxIterator it = list->cl->iterator(list, index, true); it.base.mutating = true; + return it; +} - // we know the iterators share the same memory layout - CxMutIterator iter; - memcpy(&iter, &it, sizeof(CxMutIterator)); - return iter; +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); }
--- a/src/ucx/map.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/map.c Sun Mar 02 18:10:52 2025 +0100 @@ -31,27 +31,27 @@ // <editor-fold desc="empty map implementation"> -static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) { +static void cx_empty_map_noop(cx_attr_unused CxMap *map) { // this is a noop, but MUST be implemented } static void *cx_empty_map_get( - __attribute__((__unused__)) CxMap const *map, - __attribute__((__unused__)) CxHashKey key + cx_attr_unused const CxMap *map, + cx_attr_unused CxHashKey key ) { return NULL; } -static bool cx_empty_map_iter_valid(__attribute__((__unused__)) void const *iter) { +static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) { return false; } -static CxIterator cx_empty_map_iterator( - struct cx_map_s const *map, - __attribute__((__unused__)) enum cx_map_iterator_type type +static CxMapIterator cx_empty_map_iterator( + const struct cx_map_s *map, + cx_attr_unused enum cx_map_iterator_type type ) { - CxIterator iter = {0}; - iter.src_handle = map; + CxMapIterator iter = {0}; + iter.map.c = map; iter.base.valid = cx_empty_map_iter_valid; return iter; } @@ -66,6 +66,7 @@ }; CxMap cx_empty_map = { + { NULL, NULL, 0, @@ -74,39 +75,34 @@ NULL, NULL, false, - &cx_empty_map_class + true + }, + &cx_empty_map_class }; CxMap *const cxEmptyMap = &cx_empty_map; // </editor-fold> -CxMutIterator cxMapMutIteratorValues(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); +CxMapIterator cxMapMutIteratorValues(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); it.base.mutating = true; - - // we know the iterators share the same memory layout - CxMutIterator iter; - memcpy(&iter, &it, sizeof(CxMutIterator)); - return iter; + return it; } -CxMutIterator cxMapMutIteratorKeys(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); +CxMapIterator cxMapMutIteratorKeys(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); it.base.mutating = true; - - // we know the iterators share the same memory layout - CxMutIterator iter; - memcpy(&iter, &it, sizeof(CxMutIterator)); - return iter; + return it; } -CxMutIterator cxMapMutIterator(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); +CxMapIterator cxMapMutIterator(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); it.base.mutating = true; + return it; +} - // we know the iterators share the same memory layout - CxMutIterator iter; - memcpy(&iter, &it, sizeof(CxMutIterator)); - return iter; +void cxMapFree(CxMap *map) { + if (map == NULL) return; + map->cl->deallocate(map); }
--- a/src/ucx/mempool.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/mempool.c Sun Mar 02 18:10:52 2025 +0100 @@ -27,8 +27,9 @@ */ #include "cx/mempool.h" -#include "cx/utils.h" + #include <string.h> +#include <errno.h> struct cx_mempool_memory_s { /** The destructor. */ @@ -45,18 +46,20 @@ if (pool->size >= pool->capacity) { size_t newcap = pool->capacity - (pool->capacity % 16) + 16; - struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*)); - if (newdata == NULL) { + size_t newmsize; + if (pool->capacity > newcap || cx_szmul(newcap, + sizeof(struct cx_mempool_memory_s*), &newmsize)) { + errno = EOVERFLOW; return NULL; } + struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize); + if (newdata == NULL) return NULL; pool->data = newdata; pool->capacity = newcap; } struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n); - if (mem == NULL) { - return NULL; - } + if (mem == NULL) return NULL; mem->destructor = pool->auto_destr; pool->data[pool->size] = mem; @@ -72,12 +75,11 @@ ) { size_t msz; if (cx_szmul(nelem, elsize, &msz)) { + errno = EOVERFLOW; return NULL; } void *ptr = cx_mempool_malloc(p, msz); - if (ptr == NULL) { - return NULL; - } + if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } @@ -93,17 +95,15 @@ mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); newm = realloc(mem, n + sizeof(cx_destructor_func)); - if (newm == NULL) { - return NULL; - } + if (newm == NULL) return NULL; if (mem != newm) { - cx_for_n(i, pool->size) { + for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == mem) { pool->data[i] = newm; return ((char*)newm) + sizeof(cx_destructor_func); } } - abort(); + abort(); // LCOV_EXCL_LINE } else { return ptr; } @@ -113,12 +113,13 @@ void *p, void *ptr ) { + if (!ptr) return; struct cx_mempool_s *pool = p; struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *) ((char *) ptr - sizeof(cx_destructor_func)); - cx_for_n(i, pool->size) { + for (size_t i = 0; i < pool->size; i++) { if (mem == pool->data[i]) { if (mem->destructor) { mem->destructor(mem->c); @@ -133,12 +134,13 @@ return; } } - abort(); + abort(); // LCOV_EXCL_LINE } -void cxMempoolDestroy(CxMempool *pool) { +void cxMempoolFree(CxMempool *pool) { + if (pool == NULL) return; struct cx_mempool_memory_s *mem; - cx_for_n(i, pool->size) { + for (size_t i = 0; i < pool->size; i++) { mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); @@ -157,6 +159,10 @@ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } +void cxMempoolRemoveDestructor(void *ptr) { + *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; +} + struct cx_mempool_foreign_mem_s { cx_destructor_func destr; void* mem; @@ -198,35 +204,34 @@ ) { size_t poolsize; if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { + errno = EOVERFLOW; return NULL; } struct cx_mempool_s *pool = malloc(sizeof(struct cx_mempool_s)); - if (pool == NULL) { - return NULL; - } + if (pool == NULL) return NULL; CxAllocator *provided_allocator = malloc(sizeof(CxAllocator)); - if (provided_allocator == NULL) { + if (provided_allocator == NULL) { // LCOV_EXCL_START free(pool); return NULL; - } + } // LCOV_EXCL_STOP provided_allocator->cl = &cx_mempool_allocator_class; provided_allocator->data = pool; pool->allocator = provided_allocator; pool->data = malloc(poolsize); - if (pool->data == NULL) { + if (pool->data == NULL) { // LCOV_EXCL_START free(provided_allocator); free(pool); return NULL; - } + } // LCOV_EXCL_STOP pool->size = 0; pool->capacity = capacity; pool->auto_destr = destr; - return (CxMempool *) pool; + return pool; }
--- a/src/ucx/printf.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/printf.c Sun Mar 02 18:10:52 2025 +0100 @@ -34,11 +34,12 @@ #ifndef CX_PRINTF_SBO_SIZE #define CX_PRINTF_SBO_SIZE 512 #endif +const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; int cx_fprintf( void *stream, cx_write_func wfc, - char const *fmt, + const char *fmt, ... ) { int ret; @@ -52,7 +53,7 @@ int cx_vfprintf( void *stream, cx_write_func wfc, - char const *fmt, + const char *fmt, va_list ap ) { char buf[CX_PRINTF_SBO_SIZE]; @@ -60,17 +61,21 @@ va_copy(ap2, ap); int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap); if (ret < 0) { + va_end(ap2); return ret; } else if (ret < CX_PRINTF_SBO_SIZE) { + va_end(ap2); return (int) wfc(buf, 1, ret, stream); } else { int len = ret + 1; char *newbuf = malloc(len); - if (!newbuf) { + if (!newbuf) { // LCOV_EXCL_START + va_end(ap2); return -1; - } + } // LCOV_EXCL_STOP ret = vsnprintf(newbuf, len, fmt, ap2); + va_end(ap2); if (ret > 0) { ret = (int) wfc(newbuf, 1, ret, stream); } @@ -80,21 +85,20 @@ } cxmutstr cx_asprintf_a( - CxAllocator const *allocator, - char const *fmt, + const CxAllocator *allocator, + const char *fmt, ... ) { va_list ap; - cxmutstr ret; va_start(ap, fmt); - ret = cx_vasprintf_a(allocator, fmt, ap); + cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap); va_end(ap); return ret; } cxmutstr cx_vasprintf_a( - CxAllocator const *a, - char const *fmt, + const CxAllocator *a, + const char *fmt, va_list ap ) { cxmutstr s; @@ -104,7 +108,7 @@ va_list ap2; va_copy(ap2, ap); int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap); - if (ret > 0 && ret < CX_PRINTF_SBO_SIZE) { + if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) { s.ptr = cxMalloc(a, ret + 1); if (s.ptr) { s.length = (size_t) ret; @@ -124,6 +128,67 @@ } } } + va_end(ap2); return s; } +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) { + va_list ap; + va_start(ap, fmt); + int ret = cx_vsprintf_a(alloc, str, len, fmt, ap); + va_end(ap); + return ret; +} + +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(*str, *len, fmt, ap); + if ((unsigned) ret >= *len) { + unsigned newlen = ret + 1; + char *ptr = cxRealloc(alloc, *str, newlen); + if (ptr) { + int newret = vsnprintf(ptr, newlen, fmt, ap2); + if (newret < 0) { + cxFree(alloc, ptr); + } else { + *len = newlen; + *str = ptr; + ret = newret; + } + } + } + va_end(ap2); + return ret; +} + +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) { + va_list ap; + va_start(ap, fmt); + int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap); + va_end(ap); + return ret; +} + +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, *len, fmt, ap); + *str = buf; + if ((unsigned) ret >= *len) { + unsigned newlen = ret + 1; + char *ptr = cxMalloc(alloc, newlen); + if (ptr) { + int newret = vsnprintf(ptr, newlen, fmt, ap2); + if (newret < 0) { + cxFree(alloc, ptr); + } else { + *len = newlen; + *str = ptr; + ret = newret; + } + } + } + va_end(ap2); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/properties.c Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,414 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/properties.h" + +#include <assert.h> + +const CxPropertiesConfig cx_properties_config_default = { + '=', + '#', + '\0', + '\0', + '\\', +}; + +void cxPropertiesInit( + CxProperties *prop, + CxPropertiesConfig config +) { + memset(prop, 0, sizeof(CxProperties)); + prop->config = config; +} + +void cxPropertiesDestroy(CxProperties *prop) { + cxBufferDestroy(&prop->input); + cxBufferDestroy(&prop->buffer); +} + +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +) { + if (cxBufferEof(&prop->input)) { + // destroy a possible previously initialized buffer + cxBufferDestroy(&prop->input); + cxBufferInit(&prop->input, (void*) buf, len, + NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + prop->input.size = len; + } else { + if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; + } + return 0; +} + +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +) { + cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); +} + +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +) { + // check if we have a text buffer + if (prop->input.space == NULL) { + return CX_PROPERTIES_NULL_INPUT; + } + + // a pointer to the buffer we want to read from + CxBuffer *current_buffer = &prop->input; + + // check if we have rescued data + if (!cxBufferEof(&prop->buffer)) { + // check if we can now get a complete line + cxstring input = cx_strn(prop->input.space + prop->input.pos, + prop->input.size - prop->input.pos); + cxstring nl = cx_strchr(input, '\n'); + if (nl.length > 0) { + // we add as much data to the rescue buffer as we need + // to complete the line + size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; + + if (cxBufferAppend(input.ptr, 1, + len_until_nl, &prop->buffer) < len_until_nl) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + + // advance the position in the input buffer + prop->input.pos += len_until_nl; + + // we now want to read from the rescue buffer + current_buffer = &prop->buffer; + } else { + // still not enough data, copy input buffer to internal buffer + if (cxBufferAppend(input.ptr, 1, + input.length, &prop->buffer) < input.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; + } + } + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + + // get one line and parse it + while (!cxBufferEof(current_buffer)) { + const char *buf = current_buffer->space + current_buffer->pos; + size_t len = current_buffer->size - current_buffer->pos; + + /* + * First we check if we have at least one line. We also get indices of + * delimiter and comment chars + */ + size_t delimiter_index = 0; + size_t comment_index = 0; + bool has_comment = false; + + size_t i = 0; + char c = 0; + for (; i < len; i++) { + c = buf[i]; + if (c == comment1 || c == comment2 || c == comment3) { + if (comment_index == 0) { + comment_index = i; + has_comment = true; + } + } else if (c == delimiter) { + if (delimiter_index == 0 && !has_comment) { + delimiter_index = i; + } + } else if (c == '\n') { + break; + } + } + + if (c != '\n') { + // we don't have enough data for a line, use the rescue buffer + assert(current_buffer != &prop->buffer); + // make sure that the rescue buffer does not already contain something + assert(cxBufferEof(&prop->buffer)); + if (prop->buffer.space == NULL) { + // initialize a rescue buffer, if the user did not provide one + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } else { + // from a previous rescue there might be already read data + // reset the buffer to avoid unnecessary buffer extension + cxBufferReset(&prop->buffer); + } + if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; + } + + cxstring line = has_comment ? + cx_strn(buf, comment_index) : + cx_strn(buf, i); + // check line + if (delimiter_index == 0) { + // if line is not blank ... + line = cx_strtrim(line); + // ... either no delimiter found, or key is empty + if (line.length > 0) { + if (line.ptr[0] == delimiter) { + return CX_PROPERTIES_INVALID_EMPTY_KEY; + } else { + return CX_PROPERTIES_INVALID_MISSING_DELIMITER; + } + } else { + // skip blank line + // if it was the rescue buffer, return to the original buffer + if (current_buffer == &prop->buffer) { + // assert that the rescue buffer really does not contain more data + assert(current_buffer->pos + i + 1 == current_buffer->size); + // reset the rescue buffer, but don't destroy it! + cxBufferReset(&prop->buffer); + // continue with the input buffer + current_buffer = &prop->input; + } else { + // if it was the input buffer already, just advance the position + current_buffer->pos += i + 1; + } + continue; + } + } else { + cxstring k = cx_strn(buf, delimiter_index); + cxstring val = cx_strn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + k = cx_strtrim(k); + val = cx_strtrim(val); + if (k.length > 0) { + *key = k; + *value = val; + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; + } else { + return CX_PROPERTIES_INVALID_EMPTY_KEY; + } + } + } + + // when we come to this point, all data must have been read + assert(cxBufferEof(&prop->buffer)); + assert(cxBufferEof(&prop->input)); + + return CX_PROPERTIES_NO_DATA; +} + +static int cx_properties_sink_map( + cx_attr_unused CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +) { + CxMap *map = sink->sink; + CxAllocator *alloc = sink->data; + cxmutstr v = cx_strdup_a(alloc, value); + int r = cx_map_put_cxstr(map, key, v.ptr); + if (r != 0) cx_strfree_a(alloc, &v); + return r; +} + +CxPropertiesSink cxPropertiesMapSink(CxMap *map) { + CxPropertiesSink sink; + sink.sink = map; + sink.data = (void*) cxDefaultAllocator; + sink.sink_func = cx_properties_sink_map; + return sink; +} + +static int cx_properties_read_string( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + if (prop->input.space == src->src) { + // when the input buffer already contains the string + // we have nothing more to provide + target->length = 0; + } else { + target->ptr = src->src; + target->length = src->data_size; + } + return 0; +} + +static int cx_properties_read_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + target->ptr = src->data_ptr; + target->length = fread(src->data_ptr, 1, src->data_size, src->src); + return ferror((FILE*)src->src); +} + +static int cx_properties_read_init_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + src->data_ptr = malloc(src->data_size); + if (src->data_ptr == NULL) return 1; + return 0; +} + +static void cx_properties_read_clean_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + free(src->data_ptr); +} + +CxPropertiesSource cxPropertiesStringSource(cxstring str) { + CxPropertiesSource src; + src.src = (void*) str.ptr; + src.data_size = str.length; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = len; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrSource(const char *str) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = strlen(str); + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { + CxPropertiesSource src; + src.src = file; + src.data_size = chunk_size; + src.data_ptr = NULL; + src.read_func = cx_properties_read_file; + src.read_init_func = cx_properties_read_init_file; + src.read_clean_func = cx_properties_read_clean_file; + return src; +} + +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +) { + assert(source.read_func != NULL); + assert(sink.sink_func != NULL); + + // initialize reader + if (source.read_init_func != NULL) { + if (source.read_init_func(prop, &source)) { + return CX_PROPERTIES_READ_INIT_FAILED; + } + } + + // transfer the data from the source to the sink + CxPropertiesStatus status; + CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; + bool found = false; + while (true) { + // read input + cxstring input; + if (source.read_func(prop, &source, &input)) { + status = CX_PROPERTIES_READ_FAILED; + break; + } + + // no more data - break + if (input.length == 0) { + if (found) { + // something was found, check the last kv_status + if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { + status = CX_PROPERTIES_INCOMPLETE_DATA; + } else { + status = CX_PROPERTIES_NO_ERROR; + } + } else { + // nothing found + status = CX_PROPERTIES_NO_DATA; + } + break; + } + + // set the input buffer and read the k/v-pairs + cxPropertiesFill(prop, input); + + do { + cxstring key, value; + kv_status = cxPropertiesNext(prop, &key, &value); + if (kv_status == CX_PROPERTIES_NO_ERROR) { + found = true; + if (sink.sink_func(prop, &sink, key, value)) { + kv_status = CX_PROPERTIES_SINK_FAILED; + } + } + } while (kv_status == CX_PROPERTIES_NO_ERROR); + + if (kv_status > CX_PROPERTIES_OK) { + status = kv_status; + break; + } + } + + if (source.read_clean_func != NULL) { + source.read_clean_func(prop, &source); + } + + return status; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/streams.c Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/streams.h" + +#ifndef CX_STREAM_BCOPY_BUF_SIZE +#define CX_STREAM_BCOPY_BUF_SIZE 8192 +#endif + +#ifndef CX_STREAM_COPY_BUF_SIZE +#define CX_STREAM_COPY_BUF_SIZE 1024 +#endif + +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +) { + if (n == 0) { + return 0; + } + + char *lbuf; + size_t ncp = 0; + + if (buf) { + if (bufsize == 0) return 0; + lbuf = buf; + } else { + if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; + lbuf = malloc(bufsize); + if (lbuf == NULL) return 0; + } + + size_t r; + size_t rn = bufsize > n ? n : bufsize; + while ((r = rfnc(lbuf, 1, rn, src)) != 0) { + r = wfnc(lbuf, 1, r, dest); + ncp += r; + n -= r; + rn = bufsize > n ? n : bufsize; + if (r == 0 || n == 0) { + break; + } + } + + if (lbuf != buf) { + free(lbuf); + } + + return ncp; +} + +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +) { + char buf[CX_STREAM_COPY_BUF_SIZE]; + return cx_stream_bncopy(src, dest, rfnc, wfnc, + buf, CX_STREAM_COPY_BUF_SIZE, n); +}
--- a/src/ucx/string.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/string.c Sun Mar 02 18:10:52 2025 +0100 @@ -25,19 +25,21 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - #include "cx/string.h" -#include "cx/utils.h" #include <string.h> #include <stdarg.h> -#include <ctype.h> - -#ifndef _WIN32 +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <float.h> -#include <strings.h> // for strncasecmp() - -#endif // _WIN32 +#ifdef _WIN32 +#define cx_strcasecmp_impl _strnicmp +#else +#include <strings.h> +#define cx_strcasecmp_impl strncasecmp +#endif cxmutstr cx_mutstr(char *cstring) { return (cxmutstr) {cstring, strlen(cstring)}; @@ -61,20 +63,18 @@ return (cxstring) {cstring, length}; } -cxstring cx_strcast(cxmutstr str) { - return (cxstring) {str.ptr, str.length}; -} - void cx_strfree(cxmutstr *str) { + if (str == NULL) return; free(str->ptr); str->ptr = NULL; str->length = 0; } void cx_strfree_a( - CxAllocator const *alloc, + const CxAllocator *alloc, cxmutstr *str ) { + if (str == NULL) return; cxFree(alloc, str->ptr); str->ptr = NULL; str->length = 0; @@ -89,8 +89,9 @@ va_list ap; va_start(ap, count); size_t size = 0; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring str = va_arg(ap, cxstring); + if (size > SIZE_MAX - str.length) errno = EOVERFLOW; size += str.length; } va_end(ap); @@ -99,40 +100,66 @@ } cxmutstr cx_strcat_ma( - CxAllocator const *alloc, + const CxAllocator *alloc, cxmutstr str, size_t count, ... ) { if (count == 0) return str; - cxstring *strings = calloc(count, sizeof(cxstring)); - if (!strings) abort(); + cxstring strings_stack[8]; + cxstring *strings; + if (count > 8) { + strings = calloc(count, sizeof(cxstring)); + if (strings == NULL) { + return (cxmutstr) {NULL, 0}; + } + } else { + strings = strings_stack; + } va_list ap; va_start(ap, count); // get all args and overall length + bool overflow = false; size_t slen = str.length; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = va_arg (ap, cxstring); strings[i] = s; + if (slen > SIZE_MAX - str.length) overflow = true; slen += s.length; } va_end(ap); + // abort in case of overflow + if (overflow) { + errno = EOVERFLOW; + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) { NULL, 0 }; + } + // reallocate or create new string + char *newstr; if (str.ptr == NULL) { - str.ptr = cxMalloc(alloc, slen + 1); + newstr = cxMalloc(alloc, slen + 1); } else { - str.ptr = cxRealloc(alloc, str.ptr, slen + 1); + newstr = cxRealloc(alloc, str.ptr, slen + 1); } - if (str.ptr == NULL) abort(); + if (newstr == NULL) { + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) {NULL, 0}; + } + str.ptr = newstr; // concatenate strings size_t pos = str.length; str.length = slen; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = strings[i]; memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; @@ -142,7 +169,9 @@ str.ptr[str.length] = '\0'; // free temporary array - free(strings); + if (strings != strings_stack) { + free(strings); + } return str; } @@ -191,14 +220,9 @@ cxstring string, int chr ) { - chr = 0xFF & chr; - // TODO: improve by comparing multiple bytes at once - cx_for_n(i, string.length) { - if (string.ptr[i] == chr) { - return cx_strsubs(string, i); - } - } - return (cxstring) {NULL, 0}; + char *ret = memchr(string.ptr, 0xFF & chr, string.length); + if (ret == NULL) return (cxstring) {NULL, 0}; + return (cxstring) {ret, string.length - (ret - string.ptr)}; } cxmutstr cx_strchr_m( @@ -234,8 +258,9 @@ } #ifndef CX_STRSTR_SBO_SIZE -#define CX_STRSTR_SBO_SIZE 512 +#define CX_STRSTR_SBO_SIZE 128 #endif +const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; cxstring cx_strstr( cxstring haystack, @@ -263,7 +288,7 @@ // check needle length and use appropriate prefix table // if the pattern exceeds static prefix table, allocate on the heap - bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; + const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; register size_t *ptable = useheap ? calloc(needle.length + 1, sizeof(size_t)) : s_prefix_table; @@ -302,7 +327,7 @@ } // if prefix table was allocated on the heap, free it - if (ptable != s_prefix_table) { + if (useheap) { free(ptable); } @@ -376,7 +401,7 @@ } size_t cx_strsplit_a( - CxAllocator const *allocator, + const CxAllocator *allocator, cxstring string, cxstring delim, size_t limit, @@ -418,7 +443,7 @@ } size_t cx_strsplit_ma( - CxAllocator const *allocator, + const CxAllocator *allocator, cxmutstr string, cxstring delim, size_t limit, @@ -433,10 +458,14 @@ cxstring s2 ) { if (s1.length == s2.length) { - return memcmp(s1.ptr, s2.ptr, s1.length); + return strncmp(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = strncmp(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = strncmp(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } @@ -446,38 +475,38 @@ cxstring s2 ) { if (s1.length == s2.length) { -#ifdef _WIN32 - return _strnicmp(s1.ptr, s2.ptr, s1.length); -#else - return strncasecmp(s1.ptr, s2.ptr, s1.length); -#endif + return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } int cx_strcmp_p( - void const *s1, - void const *s2 + const void *s1, + const void *s2 ) { - cxstring const *left = s1; - cxstring const *right = s2; + const cxstring *left = s1; + const cxstring *right = s2; return cx_strcmp(*left, *right); } int cx_strcasecmp_p( - void const *s1, - void const *s2 + const void *s1, + const void *s2 ) { - cxstring const *left = s1; - cxstring const *right = s2; + const cxstring *left = s1; + const cxstring *right = s2; return cx_strcasecmp(*left, *right); } -cxmutstr cx_strdup_a( - CxAllocator const *allocator, +cxmutstr cx_strdup_a_( + const CxAllocator *allocator, cxstring string ) { cxmutstr result = { @@ -493,14 +522,19 @@ return result; } +static bool str_isspace(char c) { + // TODO: remove once UCX has public API for this + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; +} + cxstring cx_strtrim(cxstring string) { cxstring result = string; // TODO: optimize by comparing multiple bytes at once - while (result.length > 0 && isspace(*result.ptr)) { + while (result.length > 0 && str_isspace(*result.ptr)) { result.ptr++; result.length--; } - while (result.length > 0 && isspace(result.ptr[result.length - 1])) { + while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) { result.length--; } return result; @@ -554,18 +588,6 @@ #endif } -void cx_strlower(cxmutstr string) { - cx_for_n(i, string.length) { - string.ptr[i] = (char) tolower(string.ptr[i]); - } -} - -void cx_strupper(cxmutstr string) { - cx_for_n(i, string.length) { - string.ptr[i] = (char) toupper(string.ptr[i]); - } -} - #ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE #define CX_STRREPLACE_INDEX_BUFFER_SIZE 64 #endif @@ -577,6 +599,8 @@ }; static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) { + // remember, the first data is on the stack! + buf = buf->next; while (buf) { struct cx_strreplace_ibuf *next = buf->next; free(buf->buf); @@ -586,51 +610,48 @@ } cxmutstr cx_strreplacen_a( - CxAllocator const *allocator, + const CxAllocator *allocator, cxstring str, - cxstring pattern, + cxstring search, cxstring replacement, size_t replmax ) { - if (pattern.length == 0 || pattern.length > str.length || replmax == 0) + if (search.length == 0 || search.length > str.length || replmax == 0) return cx_strdup_a(allocator, str); // Compute expected buffer length - size_t ibufmax = str.length / pattern.length; + size_t ibufmax = str.length / search.length; size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) { ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE; } - // Allocate first index buffer - struct cx_strreplace_ibuf *firstbuf, *curbuf; - firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf)); - if (!firstbuf) return cx_mutstrn(NULL, 0); - firstbuf->buf = calloc(ibuflen, sizeof(size_t)); - if (!firstbuf->buf) { - free(firstbuf); - return cx_mutstrn(NULL, 0); - } + // First index buffer can be on the stack + struct cx_strreplace_ibuf ibuf, *curbuf = &ibuf; + size_t ibuf_sbo[CX_STRREPLACE_INDEX_BUFFER_SIZE]; + ibuf.buf = ibuf_sbo; + ibuf.next = NULL; + ibuf.len = 0; // Search occurrences cxstring searchstr = str; size_t found = 0; do { - cxstring match = cx_strstr(searchstr, pattern); + cxstring match = cx_strstr(searchstr, search); if (match.length > 0) { // Allocate next buffer in chain, if required if (curbuf->len == ibuflen) { struct cx_strreplace_ibuf *nextbuf = calloc(1, sizeof(struct cx_strreplace_ibuf)); if (!nextbuf) { - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } nextbuf->buf = calloc(ibuflen, sizeof(size_t)); if (!nextbuf->buf) { free(nextbuf); - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } curbuf->next = nextbuf; @@ -641,8 +662,8 @@ found++; size_t idx = match.ptr - str.ptr; curbuf->buf[curbuf->len++] = idx; - searchstr.ptr = match.ptr + pattern.length; - searchstr.length = str.length - idx - pattern.length; + searchstr.ptr = match.ptr + search.length; + searchstr.length = str.length - idx - search.length; } else { break; } @@ -651,9 +672,9 @@ // Allocate result string cxmutstr result; { - ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; + long long adjlen = (long long) replacement.length - (long long) search.length; size_t rcount = 0; - curbuf = firstbuf; + curbuf = &ibuf; do { rcount += curbuf->len; curbuf = curbuf->next; @@ -661,13 +682,13 @@ result.length = str.length + rcount * adjlen; result.ptr = cxMalloc(allocator, result.length + 1); if (!result.ptr) { - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } } // Build result string - curbuf = firstbuf; + curbuf = &ibuf; size_t srcidx = 0; char *destptr = result.ptr; do { @@ -682,7 +703,7 @@ } // Copy the replacement and skip the source pattern - srcidx += pattern.length; + srcidx += search.length; memcpy(destptr, replacement.ptr, replacement.length); destptr += replacement.length; } @@ -694,12 +715,12 @@ result.ptr[result.length] = '\0'; // Free index buffer - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return result; } -CxStrtokCtx cx_strtok( +CxStrtokCtx cx_strtok_( cxstring str, cxstring delim, size_t limit @@ -717,14 +738,6 @@ return ctx; } -CxStrtokCtx cx_strtok_m( - cxmutstr str, - cxstring delim, - size_t limit -) { - return cx_strtok(cx_strcast(str), delim, limit); -} - bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -747,7 +760,7 @@ // if more delimiters are specified, check them now if (ctx->delim_more_count > 0) { - cx_for_n(i, ctx->delim_more_count) { + for (size_t i = 0; i < ctx->delim_more_count; i++) { cxstring d = cx_strstr(haystack, ctx->delim_more[i]); if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) { delim.ptr = d.ptr; @@ -777,9 +790,368 @@ void cx_strtok_delim( CxStrtokCtx *ctx, - cxstring const *delim, + const cxstring *delim, size_t count ) { ctx->delim_more = delim; ctx->delim_more_count = count; } + +#define cx_strtoX_signed_impl(rtype, rmin, rmax) \ + long long result; \ + if (cx_strtoll_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result < rmin || result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX); +} + +int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int, INT_MIN, INT_MAX); +} + +int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX); +} + +int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep) { + // strategy: parse as unsigned, check range, negate if required + bool neg = false; + size_t start_unsigned = 0; + + // emptiness check + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // test if we have a negative sign character + if (str.ptr[start_unsigned] == '-') { + neg = true; + start_unsigned++; + // must not be followed by positive sign character + if (str.length == 1 || str.ptr[start_unsigned] == '+') { + errno = EINVAL; + return -1; + } + } + + // now parse the number with strtoull + unsigned long long v; + cxstring ustr = start_unsigned == 0 ? str + : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned); + int ret = cx_strtoull_lc(ustr, &v, base, groupsep); + if (ret != 0) return ret; + if (neg) { + if (v - 1 > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = -(long long) v; + return 0; + } else { + if (v > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = (long long) v; + return 0; + } +} + +int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX); +} + +int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX); +} + +int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX); +} + +int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep) { + assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +} + +#define cx_strtoX_unsigned_impl(rtype, rmax) \ + uint64_t result; \ + if (cx_strtou64_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX); +} + +int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned int, UINT_MAX); +} + +int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX); +} + +int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep) { + // some sanity checks + if (str.length == 0) { + errno = EINVAL; + return -1; + } + if (!(base == 2 || base == 8 || base == 10 || base == 16)) { + errno = EINVAL; + return -1; + } + if (groupsep == NULL) groupsep = ""; + + // find the actual start of the number + if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + if (str.length == 0) { + errno = EINVAL; + return -1; + } + } + size_t start = 0; + + // if base is 2 or 16, some leading stuff may appear + if (base == 2) { + if ((str.ptr[0] | 32) == 'b') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'b') { + start = 2; + } + } + } else if (base == 16) { + if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'x') { + start = 2; + } + } + } + + // check if there are digits left + if (start >= str.length) { + errno = EINVAL; + return -1; + } + + // now parse the number + unsigned long long result = 0; + for (size_t i = start; i < str.length; i++) { + // ignore group separators + if (strchr(groupsep, str.ptr[i])) continue; + + // determine the digit value of the character + unsigned char c = str.ptr[i]; + if (c >= 'a') c = 10 + (c - 'a'); + else if (c >= 'A') c = 10 + (c - 'A'); + else if (c >= '0') c = c - '0'; + else c = 255; + if (c >= base) { + errno = EINVAL; + return -1; + } + + // now combine the digit with what we already have + unsigned long right = (result & 0xff) * base + c; + unsigned long long left = (result >> 8) * base + (right >> 8); + if (left > (ULLONG_MAX >> 8)) { + errno = ERANGE; + return -1; + } + result = (left << 8) + (right & 0xff); + } + + *output = result; + return 0; +} + +int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX); +} + +int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX); +} + +int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX); +} + +int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep) { + assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms + return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep); +} + +int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep) { +#if SIZE_MAX == UINT32_MAX + return cx_strtou32_lc_(str, (uint32_t*) output, base, groupsep); +#elif SIZE_MAX == UINT64_MAX + return cx_strtoull_lc_(str, (unsigned long long *) output, base, groupsep); +#else +#error "unsupported size_t size" +#endif +} + +int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep) { + // use string to double and add a range check + double d; + int ret = cx_strtod_lc_(str, &d, decsep, groupsep); + if (ret != 0) return ret; + // note: FLT_MIN is the smallest POSITIVE number that can be represented + double test = d < 0 ? -d : d; + if (test < FLT_MIN || test > FLT_MAX) { + errno = ERANGE; + return -1; + } + *output = (float) d; + return 0; +} + +static bool str_isdigit(char c) { + // TODO: remove once UCX has public API for this + return c >= '0' && c <= '9'; +} + +int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) { + // TODO: overflow check + // TODO: increase precision + + // emptiness check + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + double result = 0.; + int sign = 1; + + // check if there is a sign + if (str.ptr[0] == '-') { + sign = -1; + str.ptr++; + str.length--; + } else if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + } + + // there must be at least one char to parse + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // parse all digits until we find the decsep + size_t pos = 0; + do { + if (str_isdigit(str.ptr[pos])) { + result = result * 10 + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + + // already done? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // is the next char the decsep? + if (str.ptr[pos] == decsep) { + pos++; + // it may end with the decsep, if it did not start with it + if (pos == str.length) { + if (str.length == 1) { + errno = EINVAL; + return -1; + } else { + *output = result * sign; + return 0; + } + } + // parse everything until exponent or end + double factor = 1.; + do { + if (str_isdigit(str.ptr[pos])) { + factor *= 0.1; + result = result + factor * (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + } + + // no exponent? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // now the next separator MUST be the exponent separator + // and at least one char must follow + if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) { + errno = EINVAL; + return -1; + } + pos++; + + // check if we have a sign for the exponent + double factor = 10.; + if (str.ptr[pos] == '-') { + factor = .1; + pos++; + } else if (str.ptr[pos] == '+') { + pos++; + } + + // at least one digit must follow + if (pos == str.length) { + errno = EINVAL; + return -1; + } + + // parse the exponent + unsigned int exp = 0; + do { + if (str_isdigit(str.ptr[pos])) { + exp = 10 * exp + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + errno = EINVAL; + return -1; + } + } while (++pos < str.length); + + // apply the exponent by fast exponentiation + do { + if (exp & 1) { + result *= factor; + } + factor *= factor; + } while ((exp >>= 1) > 0); + + // store the result and exit + *output = result * sign; + return 0; +}
--- a/src/ucx/szmul.c Mon Feb 10 17:44:51 2025 +0100 +++ b/src/ucx/szmul.c Sun Mar 02 18:10:52 2025 +0100 @@ -26,6 +26,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "cx/common.h" + +#ifndef CX_SZMUL_BUILTIN int cx_szmul_impl( size_t a, size_t b, @@ -43,4 +46,5 @@ *result = 0; return 1; } -} \ No newline at end of file +} +#endif // CX_SZMUL_BUILTIN
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/tree.c Sun Mar 02 18:10:52 2025 +0100 @@ -0,0 +1,1054 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/tree.h" + +#include "cx/array_list.h" + +#include <assert.h> + +#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) +#define tree_parent(node) CX_TREE_PTR(node, loc_parent) +#define tree_children(node) CX_TREE_PTR(node, loc_children) +#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child) +#define tree_prev(node) CX_TREE_PTR(node, loc_prev) +#define tree_next(node) CX_TREE_PTR(node, loc_next) + +#define cx_tree_ptr_locations \ + loc_parent, loc_children, loc_last_child, loc_prev, loc_next + +#define cx_tree_node_layout(tree) \ + (tree)->loc_parent,\ + (tree)->loc_children,\ + (tree)->loc_last_child,\ + (tree)->loc_prev, \ + (tree)->loc_next + +static void cx_tree_zero_pointers( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + tree_parent(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } + tree_next(node) = NULL; + tree_children(node) = NULL; + if (loc_last_child >= 0) { + tree_last_child(node) = NULL; + } +} + +void cx_tree_link( + void *parent, + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert(loc_parent >= 0); + assert(loc_children >= 0); + assert(loc_next >= 0); + + void *current_parent = tree_parent(node); + if (current_parent == parent) return; + if (current_parent != NULL) { + cx_tree_unlink(node, cx_tree_ptr_locations); + } + + if (tree_children(parent) == NULL) { + tree_children(parent) = node; + if (loc_last_child >= 0) { + tree_last_child(parent) = node; + } + } else { + void *child; + if (loc_last_child >= 0) { + child = tree_last_child(parent); + tree_last_child(parent) = node; + } else { + child = tree_children(parent); + void *next; + while ((next = tree_next(child)) != NULL) { + child = next; + } + } + if (loc_prev >= 0) { + tree_prev(node) = child; + } + tree_next(child) = node; + } + tree_parent(node) = parent; +} + +static void *cx_tree_node_prev( + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_next, + const void *node +) { + void *parent = tree_parent(node); + void *begin = tree_children(parent); + if (begin == node) return NULL; + const void *cur = begin; + const void *next; + while (1) { + next = tree_next(cur); + if (next == node) return (void *) cur; + cur = next; + } +} + +void cx_tree_unlink( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (tree_parent(node) == NULL) return; + + assert(loc_children >= 0); + assert(loc_next >= 0); + assert(loc_parent >= 0); + void *left; + if (loc_prev >= 0) { + left = tree_prev(node); + } else { + left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node); + } + void *right = tree_next(node); + void *parent = tree_parent(node); + assert(left == NULL || tree_children(parent) != node); + assert(right == NULL || loc_last_child < 0 || + tree_last_child(parent) != node); + + if (left == NULL) { + tree_children(parent) = right; + } else { + tree_next(left) = right; + } + if (right == NULL) { + if (loc_last_child >= 0) { + tree_last_child(parent) = left; + } + } else { + if (loc_prev >= 0) { + tree_prev(right) = left; + } + } + + tree_parent(node) = NULL; + tree_next(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } +} + +int cx_tree_search( + const void *root, + size_t depth, + const void *node, + cx_tree_search_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + // help avoiding bugs due to uninitialized memory + assert(result != NULL); + *result = NULL; + + // remember return value for best match + int ret = sfunc(root, node); + if (ret < 0) { + // not contained, exit + return -1; + } + *result = (void*) root; + // if root is already exact match, exit + if (ret == 0) { + return 0; + } + + // when depth is one, we are already done + if (depth == 1) { + return ret; + } + + // special case: indefinite depth + if (depth == 0) { + depth = SIZE_MAX; + } + + // create an iterator + CxTreeIterator iter = cx_tree_iterator( + (void*) root, false, loc_children, loc_next + ); + + // skip root, we already handled it + cxIteratorNext(iter); + + // loop through the remaining tree + cx_foreach(void *, elem, iter) { + // investigate the current node + int ret_elem = sfunc(elem, node); + if (ret_elem == 0) { + // if found, exit the search + *result = (void *) elem; + ret = 0; + break; + } else if (ret_elem > 0 && ret_elem < ret) { + // new distance is better + *result = elem; + ret = ret_elem; + } else { + // not contained or distance is worse, skip entire subtree + cxTreeIteratorContinue(iter); + } + + // when we reached the max depth, skip the subtree + if (iter.depth == depth) { + cxTreeIteratorContinue(iter); + } + } + + // dispose the iterator as we might have exited the loop early + cxTreeIteratorDispose(&iter); + + assert(ret < 0 || *result != NULL); + return ret; +} + +int cx_tree_search_data( + const void *root, + size_t depth, + const void *data, + cx_tree_search_data_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + // it is basically the same implementation + return cx_tree_search( + root, depth, data, + (cx_tree_search_func) sfunc, + result, + loc_children, loc_next); +} + +static bool cx_tree_iter_valid(const void *it) { + const struct cx_tree_iterator_s *iter = it; + return iter->node != NULL; +} + +static void *cx_tree_iter_current(const void *it) { + const struct cx_tree_iterator_s *iter = it; + return iter->node; +} + +static void cx_tree_iter_next(void *it) { + struct cx_tree_iterator_s *iter = it; + ptrdiff_t const loc_next = iter->loc_next; + ptrdiff_t const loc_children = iter->loc_children; + // protect us from misuse + if (!iter->base.valid(iter)) return; + + void *children; + + // check if we are currently exiting or entering nodes + if (iter->exiting) { + children = NULL; + // skipping on exit is pointless, just clear the flag + iter->skip = false; + } else { + if (iter->skip) { + // skip flag is set, pretend that there are no children + iter->skip = false; + children = NULL; + } else { + // try to enter the children (if any) + children = tree_children(iter->node); + } + } + + if (children == NULL) { + // search for the next node + void *next; + cx_tree_iter_search_next: + // check if there is a sibling + if (iter->exiting) { + next = iter->node_next; + } else { + next = tree_next(iter->node); + iter->node_next = next; + } + if (next == NULL) { + // no sibling, we are done with this node and exit + if (iter->visit_on_exit && !iter->exiting) { + // iter is supposed to visit the node again + iter->exiting = true; + } else { + iter->exiting = false; + if (iter->depth == 1) { + // there is no parent - we have iterated the entire tree + // invalidate the iterator and free the node stack + iter->node = iter->node_next = NULL; + iter->stack_capacity = iter->depth = 0; + free(iter->stack); + iter->stack = NULL; + } else { + // the parent node can be obtained from the top of stack + // this way we can avoid the loc_parent in the iterator + iter->depth--; + iter->node = iter->stack[iter->depth - 1]; + // retry with the parent node to find a sibling + goto cx_tree_iter_search_next; + } + } + } else { + if (iter->visit_on_exit && !iter->exiting) { + // iter is supposed to visit the node again + iter->exiting = true; + } else { + iter->exiting = false; + // move to the sibling + iter->counter++; + iter->node = next; + // new top of stack is the sibling + iter->stack[iter->depth - 1] = next; + } + } + } else { + // node has children, push the first child onto the stack and enter it + cx_array_simple_add(iter->stack, children); + iter->node = children; + iter->counter++; + } +} + +CxTreeIterator cx_tree_iterator( + void *root, + bool visit_on_exit, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + CxTreeIterator iter; + iter.loc_children = loc_children; + iter.loc_next = loc_next; + iter.visit_on_exit = visit_on_exit; + + // initialize members + iter.node_next = NULL; + iter.exiting = false; + iter.skip = false; + + // assign base iterator functions + iter.base.mutating = false; + iter.base.remove = false; + iter.base.current_impl = NULL; + iter.base.valid = cx_tree_iter_valid; + iter.base.next = cx_tree_iter_next; + iter.base.current = cx_tree_iter_current; + + // visit the root node + iter.node = root; + if (root != NULL) { + iter.stack_capacity = 16; + iter.stack = malloc(sizeof(void *) * 16); + iter.stack[0] = root; + iter.counter = 1; + iter.depth = 1; + } else { + iter.stack_capacity = 0; + iter.stack = NULL; + iter.counter = 0; + iter.depth = 0; + } + + return iter; +} + +static bool cx_tree_visitor_valid(const void *it) { + const struct cx_tree_visitor_s *iter = it; + return iter->node != NULL; +} + +static void *cx_tree_visitor_current(const void *it) { + const struct cx_tree_visitor_s *iter = it; + return iter->node; +} + +cx_attr_nonnull +static void cx_tree_visitor_enqueue_siblings( + struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) { + node = tree_next(node); + while (node != NULL) { + struct cx_tree_visitor_queue_s *q; + q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q->depth = iter->queue_last->depth; + q->node = node; + iter->queue_last->next = q; + iter->queue_last = q; + node = tree_next(node); + } + iter->queue_last->next = NULL; +} + +static void cx_tree_visitor_next(void *it) { + struct cx_tree_visitor_s *iter = it; + // protect us from misuse + if (!iter->base.valid(iter)) return; + + ptrdiff_t const loc_next = iter->loc_next; + ptrdiff_t const loc_children = iter->loc_children; + + // add the children of the current node to the queue + // unless the skip flag is set + void *children; + if (iter->skip) { + iter->skip = false; + children = NULL; + } else { + children = tree_children(iter->node); + } + if (children != NULL) { + struct cx_tree_visitor_queue_s *q; + q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q->depth = iter->depth + 1; + q->node = children; + if (iter->queue_last == NULL) { + assert(iter->queue_next == NULL); + iter->queue_next = q; + } else { + iter->queue_last->next = q; + } + iter->queue_last = q; + cx_tree_visitor_enqueue_siblings(iter, children, loc_next); + } + + // check if there is a next node + if (iter->queue_next == NULL) { + iter->node = NULL; + return; + } + + // dequeue the next node + iter->node = iter->queue_next->node; + iter->depth = iter->queue_next->depth; + { + struct cx_tree_visitor_queue_s *q = iter->queue_next; + iter->queue_next = q->next; + if (iter->queue_next == NULL) { + assert(iter->queue_last == q); + iter->queue_last = NULL; + } + free(q); + } + + // increment the node counter + iter->counter++; +} + +CxTreeVisitor cx_tree_visitor( + void *root, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + CxTreeVisitor iter; + iter.loc_children = loc_children; + iter.loc_next = loc_next; + + // initialize members + iter.skip = false; + iter.queue_next = NULL; + iter.queue_last = NULL; + + // assign base iterator functions + iter.base.mutating = false; + iter.base.remove = false; + iter.base.current_impl = NULL; + iter.base.valid = cx_tree_visitor_valid; + iter.base.next = cx_tree_visitor_next; + iter.base.current = cx_tree_visitor_current; + + // visit the root node + iter.node = root; + if (root != NULL) { + iter.counter = 1; + iter.depth = 1; + } else { + iter.counter = 0; + iter.depth = 0; + } + + return iter; +} + +static void cx_tree_add_link_duplicate( + void *original, void *duplicate, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next +) { + void *shared_parent = tree_parent(original); + if (shared_parent == NULL) { + cx_tree_link(original, duplicate, cx_tree_ptr_locations); + } else { + cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations); + } +} + +static void cx_tree_add_link_new( + void *parent, void *node, cx_tree_search_func sfunc, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next +) { + // check the current children one by one, + // if they could be children of the new node + void *child = tree_children(parent); + while (child != NULL) { + void *next = tree_next(child); + + if (sfunc(node, child) > 0) { + // the sibling could be a child -> re-link + cx_tree_link(node, child, cx_tree_ptr_locations); + } + + child = next; + } + + // add new node as new child + cx_tree_link(parent, node, cx_tree_ptr_locations); +} + +int cx_tree_add( + const void *src, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **cnode, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + *cnode = cfunc(src, cdata); + if (*cnode == NULL) return 1; + cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations); + + void *match = NULL; + int result = cx_tree_search( + root, + 0, + *cnode, + sfunc, + &match, + loc_children, + loc_next + ); + + if (result < 0) { + // node does not fit into the tree - return non-zero value + return 1; + } else if (result == 0) { + // data already found in the tree, link duplicate + cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations); + } else { + // closest match found, add new node + cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations); + } + + return 0; +} + +unsigned int cx_tree_add_look_around_depth = 3; + +size_t cx_tree_add_iter( + struct cx_iterator_base_s *iter, + size_t num, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + // erase the failed pointer + *failed = NULL; + + // iter not valid? cancel... + if (!iter->valid(iter)) return 0; + + size_t processed = 0; + void *current_node = root; + const void *elem; + + for (void **eptr; processed < num && + iter->valid(iter) && (eptr = iter->current(iter)) != NULL; + iter->next(iter)) { + elem = *eptr; + + // create the new node + void *new_node = cfunc(elem, cdata); + if (new_node == NULL) return processed; + cx_tree_zero_pointers(new_node, cx_tree_ptr_locations); + + // start searching from current node + void *match; + int result; + unsigned int look_around_retries = cx_tree_add_look_around_depth; + cx_tree_add_look_around_retry: + result = cx_tree_search( + current_node, + 0, + new_node, + sfunc, + &match, + loc_children, + loc_next + ); + + if (result < 0) { + // traverse upwards and try to find better parents + void *parent = tree_parent(current_node); + if (parent != NULL) { + if (look_around_retries > 0) { + look_around_retries--; + current_node = parent; + } else { + // look around retries exhausted, start from the root + current_node = root; + } + goto cx_tree_add_look_around_retry; + } else { + // no parents. so we failed + *failed = new_node; + return processed; + } + } else if (result == 0) { + // data already found in the tree, link duplicate + cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations); + // but stick with the original match, in case we needed a new root + current_node = match; + } else { + // closest match found, add new node as child + cx_tree_add_link_new(match, new_node, sfunc, + cx_tree_ptr_locations); + current_node = match; + } + + processed++; + } + return processed; +} + +size_t cx_tree_add_array( + const void *src, + size_t num, + size_t elem_size, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + // erase failed pointer + *failed = NULL; + + // super special case: zero elements + if (num == 0) { + return 0; + } + + // special case: one element does not need an iterator + if (num == 1) { + void *node; + if (0 == cx_tree_add( + src, sfunc, cfunc, cdata, &node, root, + loc_parent, loc_children, loc_last_child, + loc_prev, loc_next)) { + return 1; + } else { + *failed = node; + return 0; + } + } + + // otherwise, create iterator and hand over to other function + CxIterator iter = cxIterator(src, elem_size, num); + return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc, + cfunc, cdata, failed, root, + loc_parent, loc_children, loc_last_child, + loc_prev, loc_next); +} + +static int cx_tree_default_insert_element( + CxTree *tree, + const void *data +) { + void *node; + if (tree->root == NULL) { + node = tree->node_create(data, tree); + if (node == NULL) return 1; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + tree->root = node; + tree->size = 1; + return 0; + } + int result = cx_tree_add(data, tree->search, tree->node_create, + tree, &node, tree->root, cx_tree_node_layout(tree)); + if (0 == result) { + tree->size++; + } else { + cxFree(tree->allocator, node); + } + return result; +} + +static size_t cx_tree_default_insert_many( + CxTree *tree, + CxIteratorBase *iter, + size_t n +) { + size_t ins = 0; + if (!iter->valid(iter)) return 0; + if (tree->root == NULL) { + // use the first element from the iter to create the root node + void **eptr = iter->current(iter); + void *node = tree->node_create(*eptr, tree); + if (node == NULL) return 0; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + tree->root = node; + ins = 1; + iter->next(iter); + } + void *failed; + ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, + tree, &failed, tree->root, cx_tree_node_layout(tree)); + tree->size += ins; + if (ins < n) { + cxFree(tree->allocator, failed); + } + return ins; +} + +static void *cx_tree_default_find( + CxTree *tree, + const void *subtree, + const void *data, + size_t depth +) { + if (tree->root == NULL) return NULL; + + void *found; + if (0 == cx_tree_search_data( + subtree, + depth, + data, + tree->search_data, + &found, + tree->loc_children, + tree->loc_next + )) { + return found; + } else { + return NULL; + } +} + +static cx_tree_class cx_tree_default_class = { + cx_tree_default_insert_element, + cx_tree_default_insert_many, + cx_tree_default_find +}; + +CxTree *cxTreeCreate( + const CxAllocator *allocator, + cx_tree_node_create_func create_func, + cx_tree_search_func search_func, + cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(create_func != NULL); + assert(search_func != NULL); + assert(search_data_func != NULL); + + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; + + tree->cl = &cx_tree_default_class; + tree->allocator = allocator; + tree->node_create = create_func; + tree->search = search_func; + tree->search_data = search_data_func; + tree->simple_destructor = NULL; + tree->advanced_destructor = (cx_destructor_func2) cxFree; + tree->destructor_data = (void *) allocator; + tree->loc_parent = loc_parent; + tree->loc_children = loc_children; + tree->loc_last_child = loc_last_child; + tree->loc_prev = loc_prev; + tree->loc_next = loc_next; + tree->root = NULL; + tree->size = 0; + + return tree; +} + +void cxTreeFree(CxTree *tree) { + if (tree == NULL) return; + if (tree->root != NULL) { + cxTreeClear(tree); + } + cxFree(tree->allocator, tree); +} + +CxTree *cxTreeCreateWrapped( + const CxAllocator *allocator, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(root != NULL); + + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; + + tree->cl = &cx_tree_default_class; + // set the allocator anyway, just in case... + tree->allocator = allocator; + tree->node_create = NULL; + tree->search = NULL; + tree->search_data = NULL; + tree->simple_destructor = NULL; + tree->advanced_destructor = NULL; + tree->destructor_data = NULL; + tree->loc_parent = loc_parent; + tree->loc_children = loc_children; + tree->loc_last_child = loc_last_child; + tree->loc_prev = loc_prev; + tree->loc_next = loc_next; + tree->root = root; + tree->size = cxTreeSubtreeSize(tree, root); + return tree; +} + +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +) { + size_t loc_parent = tree->loc_parent; + if (tree_parent(child) == NULL) { + tree->size++; + } + cx_tree_link(parent, child, cx_tree_node_layout(tree)); +} + +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +) { + cx_tree_link(parent, child, cx_tree_node_layout(tree)); + tree->size++; +} + +int cxTreeAddChild( + CxTree *tree, + void *parent, + const void *data) { + void *node = tree->node_create(data, tree); + if (node == NULL) return 1; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + cx_tree_link(parent, node, cx_tree_node_layout(tree)); + tree->size++; + return 0; +} + +size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) { + CxTreeVisitor visitor = cx_tree_visitor( + subtree_root, + tree->loc_children, + tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.counter; +} + +size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) { + CxTreeVisitor visitor = cx_tree_visitor( + subtree_root, + tree->loc_children, + tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.depth; +} + +size_t cxTreeDepth(CxTree *tree) { + CxTreeVisitor visitor = cx_tree_visitor( + tree->root, tree->loc_children, tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.depth; +} + +int cxTreeRemoveNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +) { + if (node == tree->root) return 1; + + // determine the new parent + ptrdiff_t loc_parent = tree->loc_parent; + void *new_parent = tree_parent(node); + + // first, unlink from the parent + cx_tree_unlink(node, cx_tree_node_layout(tree)); + + // then relink each child + ptrdiff_t loc_children = tree->loc_children; + ptrdiff_t loc_next = tree->loc_next; + void *child = tree_children(node); + while (child != NULL) { + // forcibly set the parent to NULL - we do not use the unlink function + // because that would unnecessarily modify the children linked list + tree_parent(child) = NULL; + + // update contents, if required + if (relink_func != NULL) { + relink_func(child, node, new_parent); + } + + // link to new parent + cx_tree_link(new_parent, child, cx_tree_node_layout(tree)); + + // proceed to next child + child = tree_next(child); + } + + // clear the linked list of the removed node + tree_children(node) = NULL; + ptrdiff_t loc_last_child = tree->loc_last_child; + if (loc_last_child >= 0) tree_last_child(node) = NULL; + + // the tree now has one member less + tree->size--; + + return 0; +} + +void cxTreeRemoveSubtree(CxTree *tree, void *node) { + if (node == tree->root) { + tree->root = NULL; + tree->size = 0; + return; + } + size_t subtree_size = cxTreeSubtreeSize(tree, node); + cx_tree_unlink(node, cx_tree_node_layout(tree)); + tree->size -= subtree_size; +} + +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +) { + int result = cxTreeRemoveNode(tree, node, relink_func); + if (result == 0) { + if (tree->simple_destructor) { + tree->simple_destructor(node); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, node); + } + return 0; + } else { + return result; + } +} + +void cxTreeDestroySubtree(CxTree *tree, void *node) { + cx_tree_unlink(node, cx_tree_node_layout(tree)); + CxTreeIterator iter = cx_tree_iterator( + node, true, + tree->loc_children, tree->loc_next + ); + cx_foreach(void *, child, iter) { + if (iter.exiting) { + if (tree->simple_destructor) { + tree->simple_destructor(child); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, child); + } + } + } + tree->size -= iter.counter; + if (node == tree->root) { + tree->root = NULL; + } +}
--- a/src/ucx/utils.c Mon Feb 10 17:44:51 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "cx/utils.h" - -#ifndef CX_STREAM_BCOPY_BUF_SIZE -#define CX_STREAM_BCOPY_BUF_SIZE 8192 -#endif - -#ifndef CX_STREAM_COPY_BUF_SIZE -#define CX_STREAM_COPY_BUF_SIZE 1024 -#endif - -size_t cx_stream_bncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - char *buf, - size_t bufsize, - size_t n -) { - if (n == 0) { - return 0; - } - - char *lbuf; - size_t ncp = 0; - - if (buf) { - if (bufsize == 0) return 0; - lbuf = buf; - } else { - if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; - lbuf = malloc(bufsize); - if (lbuf == NULL) { - return 0; - } - } - - size_t r; - size_t rn = bufsize > n ? n : bufsize; - while ((r = rfnc(lbuf, 1, rn, src)) != 0) { - r = wfnc(lbuf, 1, r, dest); - ncp += r; - n -= r; - rn = bufsize > n ? n : bufsize; - if (r == 0 || n == 0) { - break; - } - } - - if (lbuf != buf) { - free(lbuf); - } - - return ncp; -} - -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -) { - char buf[CX_STREAM_COPY_BUF_SIZE]; - return cx_stream_bncopy(src, dest, rfnc, wfnc, - buf, CX_STREAM_COPY_BUF_SIZE, n); -} - -#ifndef CX_SZMUL_BUILTIN -#include "szmul.c" -#endif