# HG changeset patch # User Olaf Wintermann # Date 1736198575 -3600 # Node ID 7b3a3130be4461fd73d56b57fadeac302898e38b # Parent d2bd73d28ff1d56f072470b93beff2f0e2dbca84 update ucx, toolkit diff -r d2bd73d28ff1 -r 7b3a3130be44 application/config.c --- a/application/config.c Thu Dec 12 20:01:43 2024 +0100 +++ b/application/config.c Mon Jan 06 22:22:55 2025 +0100 @@ -390,7 +390,7 @@ } free(req_url_proto.ptr); - cxListDestroy(locations); + cxListFree(locations); return id; } diff -r d2bd73d28ff1 -r 7b3a3130be44 application/download.c --- a/application/download.c Thu Dec 12 20:01:43 2024 +0100 +++ b/application/download.c Mon Jan 06 22:22:55 2025 +0100 @@ -262,7 +262,7 @@ ui_threadpool_job(download->queue, download->dialog, qthr_download_finished, download, uithr_download_finished, download); - cxListDestroy(stack); + cxListFree(stack); return 0; } diff -r d2bd73d28ff1 -r 7b3a3130be44 application/xml.c --- a/application/xml.c Thu Dec 12 20:01:43 2024 +0100 +++ b/application/xml.c Mon Jan 06 22:22:55 2025 +0100 @@ -71,7 +71,7 @@ cxBufferPut(&nsbuf, 0); // cleanup namespace map - cxMapDestroy(nsmap); + cxMapFree(nsmap); *out_xmlstr = ret; diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/config.c --- a/libidav/config.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/config.c Mon Jan 06 22:22:55 2025 +0100 @@ -266,14 +266,14 @@ if(ret != 0 && error) { *error = ret; - cxMempoolDestroy(cfg_mp); + cxMempoolFree(cfg_mp); } return config; } void dav_config_free(DavConfig *config) { - cxMempoolDestroy(config->mp); + cxMempoolFree(config->mp); } CxBuffer* dav_config2buf(DavConfig *config) { diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/davqlexec.c --- a/libidav/davqlexec.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/davqlexec.c Mon Jan 06 22:22:55 2025 +0100 @@ -287,16 +287,16 @@ CxIterator i = cxListIterator(fields); cx_foreach(DavQLField*, field, i) { if(!cx_strcmp(field->name, CX_STR("*"))) { - cxMapDestroy(properties); + cxMapFree(properties); *isallprop = 1; return create_allprop_propfind_request(); } else if(!cx_strcmp(field->name, CX_STR("-"))) { - cxMapDestroy(properties); + cxMapFree(properties); return create_propfind_request(sn, NULL, "propfind", 0); } else { if(fl_add_properties(sn, a, properties, field->expr)) { // TODO: set error - cxMapDestroy(properties); + cxMapFree(properties); return NULL; } } @@ -309,8 +309,8 @@ } CxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0); - cxListDestroy(list); - cxMapDestroy(properties); + cxListFree(list); + cxMapFree(properties); return reqbuf; } @@ -478,7 +478,7 @@ int isallprop; CxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp->allocator, st->fields, &isallprop); if(!rqbuf) { - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } cxMempoolRegister(mp, rqbuf, (cx_destructor_func)cxBufferFree); @@ -527,7 +527,7 @@ cxmutstr path = dav_format_string(mp->allocator, st->path, args, &error); if(error) { // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } @@ -537,7 +537,7 @@ CxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args); if(st->where && !where) { // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } @@ -571,7 +571,7 @@ } else { // error // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } } else if(dav_identifier2resprop(column->srctext, &resprop)) { @@ -583,7 +583,7 @@ } else { // error // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } @@ -594,7 +594,7 @@ } else { // something is broken // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } } @@ -613,7 +613,7 @@ CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, mp->allocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(!rpbuf) { // TODO: cleanup - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } @@ -686,7 +686,7 @@ result.result = NULL; result.status = -1; dav_resource_free_all(selroot); - cxListDestroy(stack); + cxListFree(stack); break; } } else { @@ -732,7 +732,7 @@ cxBufferSeek(rpbuf, SEEK_SET, 0); } - cxMempoolDestroy(mp); + cxMempoolFree(mp); return result; } diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/davqlparser.c --- a/libidav/davqlparser.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/davqlparser.c Mon Jan 06 22:22:55 2025 +0100 @@ -1839,7 +1839,7 @@ void dav_free_statement(DavQLStatement *stmt) { if(stmt->fields) { cxDefineDestructor(stmt->fields, dav_free_field); - cxListDestroy(stmt->fields); + cxListFree(stmt->fields); } if (stmt->where) { @@ -1851,10 +1851,10 @@ if(stmt->orderby) { cxDefineDestructor(stmt->orderby, dav_free_order_criterion); - cxListDestroy(stmt->orderby); + cxListFree(stmt->orderby); } if(stmt->args) { - cxListDestroy(stmt->args); + cxListFree(stmt->args); } free(stmt); } diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/methods.c --- a/libidav/methods.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/methods.c Mon Jan 06 22:22:55 2025 +0100 @@ -117,7 +117,7 @@ // deactivate header capturing and free captured map util_capture_header(handle, NULL); - cxMapDestroy(respheaders); + cxMapFree(respheaders); return ret; } @@ -271,7 +271,7 @@ // end cx_bprintf(buf, "\n\n", rootelm); - cxMapDestroy(namespaces); + cxMapFree(namespaces); return buf; } @@ -445,7 +445,7 @@ void cleanup_response(ResponseTag *result) { if(result) { - cxListDestroy(result->properties); + cxListFree(result->properties); } } @@ -733,7 +733,7 @@ } } } - cxListDestroy(properties); + cxListFree(properties); if(crypto_prop && crypto_key) { char *crypto_prop_content = util_xml_get_text(crypto_prop); @@ -949,7 +949,7 @@ cxBufferWrite(s.ptr, 1, s.length, buf); // cleanup namespace map - cxMapDestroy(namespaces); + cxMapFree(namespaces); return buf; } diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/pwdstore.c --- a/libidav/pwdstore.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/pwdstore.c Mon Jan 06 22:22:55 2025 +0100 @@ -224,11 +224,11 @@ if(ret) { pwdstore_put_index(p, id, locations); if(cxListSize(locations) == 0) { - cxListDestroy(locations); + cxListFree(locations); } } else { if(id) free(id); - cxListDestroy(locations); + cxListFree(locations); } return ret; @@ -285,12 +285,14 @@ remove_list_entries(s, id); CxHashKey key = cx_hash_key_str(id); - PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key); - PwdEntry *e = cxMapRemoveAndGet(s->ids, key); + PwdIndexEntry *i = NULL; + cxMapRemoveAndGet(s->index, key, &i); + PwdEntry *e = NULL; + cxMapRemoveAndGet(s->ids, key, &e); if(i) { if(i->locations) { - cxListDestroy(i->locations); + cxListFree(i->locations); } free(i->id); free(i); @@ -396,9 +398,9 @@ void pwdstore_free(PwdStore* p) { cxDefineDestructor(p->ids, pwdstore_free_entry); - cxMapDestroy(p->ids); + cxMapFree(p->ids); - cxListDestroy(p->locations); + cxListFree(p->locations); if(p->content) { cxBufferFree(p->content); diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/resource.c --- a/libidav/resource.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/resource.c Mon Jan 06 22:22:55 2025 +0100 @@ -131,7 +131,7 @@ // TODO: free everything dav_session_free(sn, property); } - cxMapDestroy(properties); + cxMapFree(properties); } void dav_resource_free(DavResource *res) { @@ -807,7 +807,7 @@ CxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0); int ret = dav_propfind(res->session, res, rqbuf); cxBufferFree(rqbuf); - cxMempoolDestroy(mp); + cxMempoolFree(mp); return ret; } @@ -1016,7 +1016,8 @@ cx_foreach(DavProperty *, property, i) { cxmutstr keystr = dav_property_key(property->ns->name, property->name); CxHashKey key = cx_hash_key(keystr.ptr, keystr.length); - DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key); + DavProperty *existing_prop = NULL; + cxMapRemoveAndGet(crypto_props, key, &existing_prop); cxMapPut(crypto_props, key, property); if(existing_prop) { // TODO: free existing_prop @@ -1534,7 +1535,7 @@ cxBufferPutString(content, ""); - cxMapDestroy(nsmap); + cxMapFree(nsmap); // encrypt xml document char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key); @@ -1607,7 +1608,7 @@ xmlFreeDoc(doc); if(cxMapSize(map) == 0) { - cxMapDestroy(map); + cxMapFree(map); return NULL; } return map; diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/session.c --- a/libidav/session.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/session.c Mon Jan 06 22:22:55 2025 +0100 @@ -211,7 +211,21 @@ char *log_method; char *log_url; curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url); +#if LIBCURL_VERSION_NUM >= 0x074800 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method); +#else + long opt_upload = 0; + curl_easy_getinfo(sn->handle, CURLOPT_UPLOAD, &opt_upload); + char *opt_custom = NULL; + curl_easy_getinfo(sn->handle, CURLOPT_CUSTOMREQUEST, &opt_custom); + if(opt_custom) { + log_method = opt_custom; + } else if(opt_upload) { + log_method = "PUT"; + } else { + log_method = "GET"; + } +#endif char *log_reqbody = NULL; size_t log_reqbodylen = 0; char *log_rpbody = NULL; @@ -337,7 +351,7 @@ } void dav_session_destructor(DavSession *sn) { - cxMempoolDestroy(sn->mp); + cxMempoolFree(sn->mp); curl_easy_cleanup(sn->handle); free(sn); } @@ -632,7 +646,7 @@ return; } - if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) { + if(!cxMapRemove(locks->resource_locks, cx_hash_key_str(path))) { return; } diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/versioning.c --- a/libidav/versioning.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/versioning.c Mon Jan 06 22:22:55 2025 +0100 @@ -158,7 +158,7 @@ cx_foreach(DavProperty*, p, i) { free(p->name); } - cxListDestroy(proplist); + cxListFree(proplist); } if(error && versions) { diff -r d2bd73d28ff1 -r 7b3a3130be44 libidav/webdav.c --- a/libidav/webdav.c Thu Dec 12 20:01:43 2024 +0100 +++ b/libidav/webdav.c Mon Jan 06 22:22:55 2025 +0100 @@ -102,7 +102,7 @@ void dav_context_destroy(DavContext *ctx) { // destroy all sessions assoziated with this context // ctx->sessions destructor must be dav_session_destructor - cxListDestroy(ctx->sessions); + cxListFree(ctx->sessions); if(ctx->http_proxy) { free(ctx->http_proxy); @@ -123,7 +123,7 @@ } free(ns); } - cxMapDestroy(ctx->namespaces); + cxMapFree(ctx->namespaces); } if(ctx->namespaceinfo) { // TODO: implement @@ -140,7 +140,7 @@ } free(key); } - cxMapDestroy(ctx->keys); + cxMapFree(ctx->keys); } free(ctx); @@ -418,7 +418,7 @@ cx_foreach(DavProperty*, p, i) { free(p->name); } - cxListDestroy(proplist); + cxListFree(proplist); } return resource; diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/Makefile --- a/ucx/Makefile Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/Makefile Mon Jan 06 22:22:55 2025 +0100 @@ -26,7 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT = ../ include ../config.mk # list of source files @@ -37,14 +36,16 @@ SRC += compare.c SRC += hash_key.c SRC += hash_map.c +SRC += iterator.c SRC += linked_list.c SRC += list.c SRC += map.c SRC += printf.c SRC += string.c -SRC += utils.c SRC += tree.c -SRC += iterator.c +SRC += streams.c +SRC += properties.c +SRC += json.c OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT)) diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/allocator.c --- a/ucx/allocator.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/allocator.c Mon Jan 06 22:22:55 2025 +0100 @@ -28,35 +28,33 @@ #include "cx/allocator.h" -__attribute__((__malloc__, __alloc_size__(2))) +#include + static void *cx_malloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, size_t n ) { return malloc(n); } -__attribute__((__warn_unused_result__, __alloc_size__(3))) static void *cx_realloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem, size_t n ) { return realloc(mem, n); } -__attribute__((__malloc__, __alloc_size__(2, 3))) static void *cx_calloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, size_t nelem, size_t n ) { return calloc(nelem, n); } -__attribute__((__nonnull__)) static void cx_free_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem ) { free(mem); @@ -75,20 +73,41 @@ }; CxAllocator *cxDefaultAllocator = &cx_default_allocator; - +#undef cx_reallocate int cx_reallocate( void **mem, size_t n ) { void *nmem = realloc(*mem, n); if (nmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } else { *mem = nmem; return 0; } } +#undef cx_reallocatearray +int cx_reallocatearray( + void **mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return 1; + } else { + void *nmem = realloc(*mem, n); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } + } +} + // IMPLEMENTATION OF HIGH LEVEL API void *cxMalloc( @@ -106,6 +125,22 @@ return allocator->cl->realloc(allocator->data, mem, n); } +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return NULL; + } else { + return allocator->cl->realloc(allocator->data, mem, n); + } +} + +#undef cxReallocate int cxReallocate( const CxAllocator *allocator, void **mem, @@ -113,7 +148,23 @@ ) { void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } +} + +#undef cxReallocateArray +int cxReallocateArray( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +) { + void *nmem = cxReallocArray(allocator, *mem, nmemb, size); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE } else { *mem = nmem; return 0; diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/array_list.c --- a/ucx/array_list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/array_list.c Mon Jan 06 22:22:55 2025 +0100 @@ -30,6 +30,7 @@ #include "cx/compare.h" #include #include +#include // Default array reallocator @@ -37,65 +38,258 @@ void *array, size_t capacity, size_t elem_size, - __attribute__((__unused__)) struct cx_array_reallocator_s *alloc + cx_attr_unused CxArrayReallocator *alloc ) { - return realloc(array, capacity * elem_size); + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + return realloc(array, n); } -struct cx_array_reallocator_s cx_array_default_reallocator_impl = { +CxArrayReallocator cx_array_default_reallocator_impl = { cx_array_default_realloc, NULL, NULL, 0, 0 }; -struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl; +CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; + +// Stack-aware array reallocator + +static void *cx_array_advanced_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + // check for overflow + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + + // retrieve the pointer to the actual allocator + const CxAllocator *al = alloc->ptr1; + + // check if the array is still located on the stack + void *newmem; + if (array == alloc->ptr2) { + newmem = cxMalloc(al, n); + if (newmem != NULL && array != NULL) { + memcpy(newmem, array, n); + } + } else { + newmem = cxRealloc(al, array, n); + } + return newmem; +} + +struct cx_array_reallocator_s cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + return (struct cx_array_reallocator_s) { + cx_array_advanced_realloc, + (void*) allocator, (void*) stackmem, + 0, 0 + }; +} // LOW LEVEL ARRAY LIST FUNCTIONS -enum cx_array_result cx_array_copy( +static size_t cx_array_align_capacity( + size_t cap, + size_t alignment, + size_t max +) { + if (cap > max - alignment) { + return cap; + } else { + return cap - (cap % alignment) + alignment; + } +} + +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + // assert pointers + assert(array != NULL); + assert(size != NULL); + assert(capacity != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*array != NULL || oldcap == 0); + + // check for overflow + if (elem_count > max_size - oldsize) { + errno = EOVERFLOW; + return 1; + } + + // determine new capacity + size_t newcap = oldsize + elem_count; + + // reallocate if possible + if (newcap > oldcap) { + // calculate new capacity (next number divisible by 16) + newcap = cx_array_align_capacity(newcap, 16, max_size); + + // perform reallocation + void *newmem = reallocator->realloc( + *array, newcap, elem_size, reallocator + ); + if (newmem == NULL) { + return 1; // LCOV_EXCL_LINE + } + + // store new pointer + *array = newmem; + + // store new capacity + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + } +#endif + } + + return 0; +} + +int cx_array_copy( void **target, - size_t *size, - size_t *capacity, + void *size, + void *capacity, + unsigned width, size_t index, const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ) { // assert pointers assert(target != NULL); assert(size != NULL); + assert(capacity != NULL); assert(src != NULL); - // determine capacity - size_t cap = capacity == NULL ? *size : *capacity; + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*target != NULL || oldcap == 0); + + // check for overflow + if (index > max_size || elem_count > max_size - index) { + errno = EOVERFLOW; + return 1; + } // check if resize is required size_t minsize = index + elem_count; - size_t newsize = *size < minsize ? minsize : *size; - bool needrealloc = newsize > cap; + size_t newsize = oldsize < minsize ? minsize : oldsize; // reallocate if possible - if (needrealloc) { - // a reallocator and a capacity variable must be available - if (reallocator == NULL || capacity == NULL) { - return CX_ARRAY_REALLOC_NOT_SUPPORTED; - } - + size_t newcap = oldcap; + if (newsize > oldcap) { // check, if we need to repair the src pointer uintptr_t targetaddr = (uintptr_t) *target; uintptr_t srcaddr = (uintptr_t) src; bool repairsrc = targetaddr <= srcaddr - && srcaddr < targetaddr + cap * elem_size; + && srcaddr < targetaddr + oldcap * elem_size; // calculate new capacity (next number divisible by 16) - cap = newsize - (newsize % 16) + 16; - assert(cap > newsize); + newcap = cx_array_align_capacity(newsize, 16, max_size); + assert(newcap > newsize); // perform reallocation void *newmem = reallocator->realloc( - *target, cap, elem_size, reallocator + *target, newcap, elem_size, reallocator ); if (newmem == NULL) { - return CX_ARRAY_REALLOC_FAILED; + return 1; } // repair src pointer, if necessary @@ -103,9 +297,8 @@ src = ((char *) newmem) + (srcaddr - targetaddr); } - // store new pointer and capacity + // store new pointer *target = newmem; - *capacity = cap; } // determine target pointer @@ -113,14 +306,34 @@ start += index * elem_size; // copy elements and set new size + // note: no overflow check here, b/c we cannot get here w/o allocation memmove(start, src, elem_count * elem_size); - *size = newsize; + + // if any of size or capacity changed, store them back + if (newsize != oldsize || newcap != oldcap) { + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + *(size_t*) size = newsize; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + *(uint16_t*) size = (uint16_t) newsize; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + *(uint8_t*) size = (uint8_t) newsize; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + *(uint32_t*) size = (uint32_t) newsize; + } +#endif + } // return successfully - return CX_ARRAY_SUCCESS; + return 0; } -enum cx_array_result cx_array_insert_sorted( +int cx_array_insert_sorted( void **target, size_t *size, size_t *capacity, @@ -128,7 +341,7 @@ const void *sorted_data, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ) { // assert pointers assert(target != NULL); @@ -136,25 +349,35 @@ assert(capacity != NULL); assert(cmp_func != NULL); assert(sorted_data != NULL); - assert(reallocator != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } // corner case if (elem_count == 0) return 0; + // overflow check + if (elem_count > SIZE_MAX - *size) { + errno = EOVERFLOW; + return 1; + } + // store some counts size_t old_size = *size; size_t needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation if (needed_capacity > *capacity) { - size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16; + size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); void *new_mem = reallocator->realloc( *target, new_capacity, elem_size, reallocator ); if (new_mem == NULL) { // give it up right away, there is no contract // that requires us to insert as much as we can - return CX_ARRAY_REALLOC_FAILED; + return 1; // LCOV_EXCL_LINE } *target = new_mem; *capacity = new_capacity; @@ -228,7 +451,7 @@ // still buffer elements left? // don't worry, we already moved them to the correct place - return CX_ARRAY_SUCCESS; + return 0; } size_t cx_array_binary_search_inf( @@ -255,6 +478,9 @@ return 0; } + // special case: there is only one element and that is smaller + if (size == 1) return 0; + // check the last array element result = cmp_func(elem, array + elem_size * (size - 1)); if (result >= 0) { @@ -287,10 +513,48 @@ return result < 0 ? (pivot_index - 1) : pivot_index; } +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t index = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (index < size && + cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { + return index; + } else { + return size; + } +} + +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t inf = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (inf == size) { + // no infimum means, first element is supremum + return 0; + } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { + return inf; + } else { + return inf + 1; + } +} + #ifndef CX_ARRAY_SWAP_SBO_SIZE #define CX_ARRAY_SWAP_SBO_SIZE 128 #endif -unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; +const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; void cx_array_swap( void *arr, @@ -337,22 +601,9 @@ struct cx_list_s base; void *data; size_t capacity; - struct cx_array_reallocator_s reallocator; + CxArrayReallocator reallocator; } cx_array_list; -static void *cx_arl_realloc( - void *array, - size_t capacity, - size_t elem_size, - struct cx_array_reallocator_s *alloc -) { - // retrieve the pointer to the list allocator - const CxAllocator *al = alloc->ptr1; - - // use the list allocator to reallocate the memory - return cxRealloc(al, array, capacity * elem_size); -} - static void cx_arl_destructor(struct cx_list_s *list) { cx_array_list *arl = (cx_array_list *) list; @@ -394,10 +645,11 @@ size_t elems_to_move = list->collection.size - index; size_t start_of_moved = index + n; - if (CX_ARRAY_SUCCESS != cx_array_copy( + if (cx_array_copy( &arl->data, &list->collection.size, &arl->capacity, + 0, start_of_moved, first_to_move, list->collection.elem_size, @@ -414,20 +666,21 @@ // therefore, it is impossible to leave this function with an invalid array // place the new elements - if (CX_ARRAY_SUCCESS == cx_array_copy( + if (cx_array_copy( &arl->data, &list->collection.size, &arl->capacity, + 0, index, array, list->collection.elem_size, n, &arl->reallocator )) { - return n; - } else { // array list implementation is "all or nothing" return 0; + } else { + return n; } } @@ -439,7 +692,7 @@ // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; - if (CX_ARRAY_SUCCESS == cx_array_insert_sorted( + if (cx_array_insert_sorted( &arl->data, &list->collection.size, &arl->capacity, @@ -449,10 +702,10 @@ n, &arl->reallocator )) { - return n; - } else { // array list implementation is "all or nothing" return 0; + } else { + return n; } } @@ -494,45 +747,66 @@ } } -static int cx_arl_remove( +static size_t cx_arl_remove( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ) { cx_array_list *arl = (cx_array_list *) list; // out-of-bounds check + size_t remove; if (index >= list->collection.size) { - return 1; + remove = 0; + } else if (index + num > list->collection.size) { + remove = list->collection.size - index; + } else { + remove = num; } - // content destruction - cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size); + // easy exit + if (remove == 0) return 0; - // short-circuit removal of last element - if (index == list->collection.size - 1) { - list->collection.size--; - return 0; + // destroy or copy contents + if (targetbuf == NULL) { + for (size_t idx = index; idx < index + remove; idx++) { + cx_invoke_destructor( + list, + ((char *) arl->data) + idx * list->collection.elem_size + ); + } + } else { + memcpy( + targetbuf, + ((char *) arl->data) + index * list->collection.elem_size, + remove * list->collection.elem_size + ); } - // just move the elements starting at index to the left - int result = cx_array_copy( + // short-circuit removal of last elements + if (index + remove == list->collection.size) { + list->collection.size -= remove; + return remove; + } + + // just move the elements to the left + cx_array_copy( &arl->data, &list->collection.size, &arl->capacity, + 0, index, - ((char *) arl->data) + (index + 1) * list->collection.elem_size, + ((char *) arl->data) + (index + remove) * list->collection.elem_size, list->collection.elem_size, - list->collection.size - index - 1, + list->collection.size - index - remove, &arl->reallocator ); - // cx_array_copy cannot fail, array cannot grow - assert(result == 0); + // decrease the size + list->collection.size -= remove; - // decrease the size - list->collection.size--; - - return 0; + return remove; } static void cx_arl_clear(struct cx_list_s *list) { @@ -594,10 +868,11 @@ for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) { if (0 == list->collection.cmpfunc(elem, cur)) { if (remove) { - if (0 == cx_arl_remove(list, i)) { + if (1 == cx_arl_remove(list, i, 1, NULL)) { return i; } else { - return -1; + // should be unreachable + return -1; // LCOV_EXCL_LINE } } else { return i; @@ -664,7 +939,7 @@ struct cx_iterator_s *iter = it; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index); + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } else { iter->index++; iter->elem_handle = @@ -678,7 +953,7 @@ const cx_array_list *list = iter->src_handle.c; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index); + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } iter->index--; if (iter->index < list->base.collection.size) { @@ -754,14 +1029,13 @@ // allocate the array after the real elem_size is known list->data = cxCalloc(allocator, initial_capacity, elem_size); - if (list->data == NULL) { + if (list->data == NULL) { // LCOV_EXCL_START cxFree(allocator, list); return NULL; - } + } // LCOV_EXCL_STOP // configure the reallocator - list->reallocator.realloc = cx_arl_realloc; - list->reallocator.ptr1 = (void *) allocator; + list->reallocator = cx_array_reallocator(allocator, NULL); return (CxList *) list; } diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/buffer.c --- a/ucx/buffer.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/buffer.c Mon Jan 06 22:22:55 2025 +0100 @@ -27,10 +27,21 @@ */ #include "cx/buffer.h" -#include "cx/utils.h" #include #include +#include + +static int buffer_copy_on_write(CxBuffer* buffer) { + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; + void *newspace = cxMalloc(buffer->allocator, buffer->capacity); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; +} int cxBufferInit( CxBuffer *buffer, @@ -39,13 +50,18 @@ const CxAllocator *allocator, int flags ) { - if (allocator == NULL) allocator = cxDefaultAllocator; + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (flags & CX_BUFFER_COPY_ON_EXTEND) { + flags |= CX_BUFFER_AUTO_EXTEND; + } buffer->allocator = allocator; buffer->flags = flags; if (!space) { buffer->bytes = cxMalloc(allocator, capacity); if (buffer->bytes == NULL) { - return 1; + return -1; // LCOV_EXCL_LINE } buffer->flags |= CX_BUFFER_FREE_CONTENTS; } else { @@ -55,19 +71,27 @@ buffer->size = 0; buffer->pos = 0; - buffer->flush_func = NULL; - buffer->flush_target = NULL; - buffer->flush_blkmax = 0; - buffer->flush_blksize = 4096; - buffer->flush_threshold = SIZE_MAX; + buffer->flush = NULL; return 0; } +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config +) { + buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE + memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); + return 0; +} + void cxBufferDestroy(CxBuffer *buffer) { - if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { + if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } + free(buffer->flush); + memset(buffer, 0, sizeof(CxBuffer)); } CxBuffer *cxBufferCreate( @@ -76,21 +100,26 @@ const CxAllocator *allocator, int flags ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); if (buf == NULL) return NULL; if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { return buf; } else { + // LCOV_EXCL_START cxFree(allocator, buf); return NULL; + // LCOV_EXCL_STOP } } void cxBufferFree(CxBuffer *buffer) { - if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { - cxFree(buffer->allocator, buffer->bytes); - } - cxFree(buffer->allocator, buffer); + if (buffer == NULL) return; + const CxAllocator *allocator = buffer->allocator; + cxBufferDestroy(buffer); + cxFree(allocator, buffer); } int cxBufferSeek( @@ -117,10 +146,11 @@ npos += offset; if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { + errno = EOVERFLOW; return -1; } - if (npos >= buffer->size) { + if (npos > buffer->size) { return -1; } else { buffer->pos = npos; @@ -130,7 +160,9 @@ } void cxBufferClear(CxBuffer *buffer) { - memset(buffer->bytes, 0, buffer->size); + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) { + memset(buffer->bytes, 0, buffer->size); + } buffer->size = 0; buffer->pos = 0; } @@ -140,7 +172,7 @@ buffer->pos = 0; } -int cxBufferEof(const CxBuffer *buffer) { +bool cxBufferEof(const CxBuffer *buffer) { return buffer->pos >= buffer->size; } @@ -152,48 +184,71 @@ return 0; } - if (cxReallocate(buffer->allocator, + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + void *newspace = cxMalloc(buffer->allocator, newcap); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->capacity = newcap; + buffer->flags &= ~force_copy_flags; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; + } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { buffer->capacity = newcap; return 0; } else { - return -1; + return -1; // LCOV_EXCL_LINE } } -/** - * Helps flushing data to the flush target of a buffer. - * - * @param buffer the buffer containing the config - * @param space the data to flush - * @param size the element size - * @param nitems the number of items - * @return the number of items flushed - */ -static size_t cx_buffer_write_flush_helper( - CxBuffer *buffer, - const unsigned char *space, +static size_t cx_buffer_flush_helper( + const CxBuffer *buffer, size_t size, + const unsigned char *src, size_t nitems ) { - size_t pos = 0; - size_t remaining = nitems; - size_t max_items = buffer->flush_blksize / size; - while (remaining > 0) { - size_t items = remaining > max_items ? max_items : remaining; - size_t flushed = buffer->flush_func( - space + pos, - size, items, - buffer->flush_target); + // flush data from an arbitrary source + // does not need to be the buffer's contents + size_t max_items = buffer->flush->blksize / size; + size_t fblocks = 0; + size_t flushed_total = 0; + while (nitems > 0 && fblocks < buffer->flush->blkmax) { + fblocks++; + size_t items = nitems > max_items ? max_items : nitems; + size_t flushed = buffer->flush->wfunc( + src, size, items, buffer->flush->target); if (flushed > 0) { - pos += (flushed * size); - remaining -= flushed; + flushed_total += flushed; + src += flushed * size; + nitems -= flushed; } else { // if no bytes can be flushed out anymore, we give up break; } } - return nitems - remaining; + return flushed_total; +} + +static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { + // flush the current contents of the buffer + unsigned char *space = buffer->bytes; + size_t remaining = buffer->pos / size; + size_t flushed_total = cx_buffer_flush_helper( + buffer, size, space, remaining); + + // shift the buffer left after flushing + // IMPORTANT: up to this point, copy on write must have been + // performed already, because we can't do error handling here + cxBufferShiftLeft(buffer, flushed_total*size); + + return flushed_total; +} + +size_t cxBufferFlush(CxBuffer *buffer) { + if (buffer_copy_on_write(buffer)) return 0; + return cx_buffer_flush_impl(buffer, 1); } size_t cxBufferWrite( @@ -204,6 +259,7 @@ ) { // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { + if (buffer_copy_on_write(buffer)) return 0; memcpy(buffer->bytes + buffer->pos, ptr, nitems); buffer->pos += nitems; if (buffer->pos > buffer->size) { @@ -213,80 +269,69 @@ } size_t len; - size_t nitems_out = nitems; if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; + return 0; + } + if (buffer->pos > SIZE_MAX - len) { + errno = EOVERFLOW; return 0; } size_t required = buffer->pos + len; - if (buffer->pos > required) { - return 0; - } bool perform_flush = false; if (required > buffer->capacity) { - if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { - if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + if (buffer->flush != NULL && required > buffer->flush->threshold) { perform_flush = true; } else { if (cxBufferMinimumCapacity(buffer, required)) { - return 0; + return 0; // LCOV_EXCL_LINE } } } else { - if (buffer->flush_blkmax > 0) { + if (buffer->flush != NULL) { perform_flush = true; } else { - // truncate data to be written, if we can neither extend nor flush + // truncate data, if we can neither extend nor flush len = buffer->capacity - buffer->pos; if (size > 1) { len -= len % size; } - nitems_out = len / size; + nitems = len / size; } } } + // check here and not above because of possible truncation if (len == 0) { - return len; + return 0; } - if (perform_flush) { - size_t flush_max; - if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { - return 0; - } - size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL - ? buffer->pos - : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); - if (flush_pos == buffer->pos) { - // entire buffer has been flushed, we can reset - buffer->size = buffer->pos = 0; - - size_t items_flush; // how many items can also be directly flushed - size_t items_keep; // how many items have to be written to the buffer + // check if we need to copy + if (buffer_copy_on_write(buffer)) return 0; - items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; - if (items_flush > 0) { - items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); - // in case we could not flush everything, keep the rest + // perform the operation + if (perform_flush) { + size_t items_flush; + if (buffer->pos == 0) { + // if we don't have data in the buffer, but are instructed + // to flush, it means that we are supposed to relay the data + items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); + if (items_flush == 0) { + // we needed to flush, but could not flush anything + // give up and avoid endless trying + return 0; } - items_keep = nitems - items_flush; - if (items_keep > 0) { - // try again with the remaining stuff - const unsigned char *new_ptr = ptr; - new_ptr += items_flush * size; - // report the directly flushed items as written plus the remaining stuff - return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); - } else { - // all items have been flushed - report them as written - return nitems; + size_t ritems = nitems - items_flush; + const unsigned char *rest = ptr; + rest += items_flush * size; + return items_flush + cxBufferWrite(rest, size, ritems, buffer); + } else { + items_flush = cx_buffer_flush_impl(buffer, size); + if (items_flush == 0) { + return 0; } - } else if (flush_pos == 0) { - // nothing could be flushed at all, we immediately give up without writing any data - return 0; - } else { - // we were partially successful, we shift left and try again - cxBufferShiftLeft(buffer, flush_pos); return cxBufferWrite(ptr, size, nitems, buffer); } } else { @@ -295,11 +340,24 @@ if (buffer->pos > buffer->size) { buffer->size = buffer->pos; } - return nitems_out; + return nitems; } } +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { + size_t pos = buffer->pos; + buffer->pos = buffer->size; + size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; + return written; +} + int cxBufferPut( CxBuffer *buffer, int c @@ -313,6 +371,17 @@ } } +int cxBufferTerminate(CxBuffer *buffer) { + bool success = 0 == cxBufferPut(buffer, 0); + if (success) { + buffer->pos--; + buffer->size--; + return 0; + } else { + return -1; + } +} + size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -328,6 +397,7 @@ ) { size_t len; if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; return 0; } if (buffer->pos + len > buffer->size) { @@ -362,6 +432,7 @@ if (shift >= buffer->size) { buffer->pos = buffer->size = 0; } else { + if (buffer_copy_on_write(buffer)) return -1; memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); buffer->size -= shift; @@ -378,14 +449,18 @@ CxBuffer *buffer, size_t shift ) { + if (buffer->size > SIZE_MAX - shift) { + errno = EOVERFLOW; + return -1; + } size_t req_capacity = buffer->size + shift; size_t movebytes; // auto extend buffer, if required and enabled if (buffer->capacity < req_capacity) { - if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { if (cxBufferMinimumCapacity(buffer, req_capacity)) { - return 1; + return -1; // LCOV_EXCL_LINE } movebytes = buffer->size; } else { @@ -395,8 +470,11 @@ movebytes = buffer->size; } - memmove(buffer->bytes + shift, buffer->bytes, movebytes); - buffer->size = shift + movebytes; + if (movebytes > 0) { + if (buffer_copy_on_write(buffer)) return -1; + memmove(buffer->bytes + shift, buffer->bytes, movebytes); + buffer->size = shift + movebytes; + } buffer->pos += shift; if (buffer->pos > buffer->size) { diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/compare.c --- a/ucx/compare.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/compare.c Mon Jan 06 22:22:55 2025 +0100 @@ -30,9 +30,21 @@ #include +int cx_vcmp_int(int a, int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + int cx_cmp_int(const void *i1, const void *i2) { int a = *((const int *) i1); int b = *((const int *) i2); + return cx_vcmp_int(a, b); +} + +int cx_vcmp_longint(long int a, long int b) { if (a == b) { return 0; } else { @@ -43,6 +55,10 @@ int cx_cmp_longint(const void *i1, const void *i2) { long int a = *((const long int *) i1); long int b = *((const long int *) i2); + return cx_vcmp_longint(a, b); +} + +int cx_vcmp_longlong(long long a, long long b) { if (a == b) { return 0; } else { @@ -53,6 +69,10 @@ int cx_cmp_longlong(const void *i1, const void *i2) { long long a = *((const long long *) i1); long long b = *((const long long *) i2); + return cx_vcmp_longlong(a, b); +} + +int cx_vcmp_int16(int16_t a, int16_t b) { if (a == b) { return 0; } else { @@ -63,6 +83,10 @@ int cx_cmp_int16(const void *i1, const void *i2) { int16_t a = *((const int16_t *) i1); int16_t b = *((const int16_t *) i2); + return cx_vcmp_int16(a, b); +} + +int cx_vcmp_int32(int32_t a, int32_t b) { if (a == b) { return 0; } else { @@ -73,6 +97,10 @@ int cx_cmp_int32(const void *i1, const void *i2) { int32_t a = *((const int32_t *) i1); int32_t b = *((const int32_t *) i2); + return cx_vcmp_int32(a, b); +} + +int cx_vcmp_int64(int64_t a, int64_t b) { if (a == b) { return 0; } else { @@ -83,6 +111,10 @@ int cx_cmp_int64(const void *i1, const void *i2) { int64_t a = *((const int64_t *) i1); int64_t b = *((const int64_t *) i2); + return cx_vcmp_int64(a, b); +} + +int cx_vcmp_uint(unsigned int a, unsigned int b) { if (a == b) { return 0; } else { @@ -93,6 +125,10 @@ int cx_cmp_uint(const void *i1, const void *i2) { unsigned int a = *((const unsigned int *) i1); unsigned int b = *((const unsigned int *) i2); + return cx_vcmp_uint(a, b); +} + +int cx_vcmp_ulongint(unsigned long int a, unsigned long int b) { if (a == b) { return 0; } else { @@ -103,6 +139,10 @@ int cx_cmp_ulongint(const void *i1, const void *i2) { unsigned long int a = *((const unsigned long int *) i1); unsigned long int b = *((const unsigned long int *) i2); + return cx_vcmp_ulongint(a, b); +} + +int cx_vcmp_ulonglong(unsigned long long a, unsigned long long b) { if (a == b) { return 0; } else { @@ -113,6 +153,10 @@ int cx_cmp_ulonglong(const void *i1, const void *i2) { unsigned long long a = *((const unsigned long long *) i1); unsigned long long b = *((const unsigned long long *) i2); + return cx_vcmp_ulonglong(a, b); +} + +int cx_vcmp_uint16(uint16_t a, uint16_t b) { if (a == b) { return 0; } else { @@ -123,6 +167,10 @@ int cx_cmp_uint16(const void *i1, const void *i2) { uint16_t a = *((const uint16_t *) i1); uint16_t b = *((const uint16_t *) i2); + return cx_vcmp_uint16(a, b); +} + +int cx_vcmp_uint32(uint32_t a, uint32_t b) { if (a == b) { return 0; } else { @@ -133,6 +181,10 @@ int cx_cmp_uint32(const void *i1, const void *i2) { uint32_t a = *((const uint32_t *) i1); uint32_t b = *((const uint32_t *) i2); + return cx_vcmp_uint32(a, b); +} + +int cx_vcmp_uint64(uint64_t a, uint64_t b) { if (a == b) { return 0; } else { @@ -143,7 +195,11 @@ int cx_cmp_uint64(const void *i1, const void *i2) { uint64_t a = *((const uint64_t *) i1); uint64_t b = *((const uint64_t *) i2); - if (a == b) { + return cx_vcmp_uint64(a, b); +} + +int cx_vcmp_float(float a, float b) { + if (fabsf(a - b) < 1e-6f) { return 0; } else { return a < b ? -1 : 1; @@ -153,7 +209,11 @@ int cx_cmp_float(const void *f1, const void *f2) { float a = *((const float *) f1); float b = *((const float *) f2); - if (fabsf(a - b) < 1e-6f) { + return cx_vcmp_float(a, b); +} + +int cx_vcmp_double(double a, double b) { + if (fabs(a - b) < 1e-14) { return 0; } else { return a < b ? -1 : 1; @@ -166,10 +226,14 @@ ) { double a = *((const double *) d1); double b = *((const double *) d2); - if (fabs(a - b) < 1e-14) { + return cx_vcmp_double(a, b); +} + +int cx_vcmp_intptr(intptr_t p1, intptr_t p2) { + if (p1 == p2) { return 0; } else { - return a < b ? -1 : 1; + return p1 < p2 ? -1 : 1; } } @@ -179,6 +243,10 @@ ) { intptr_t p1 = *(const intptr_t *) ptr1; intptr_t p2 = *(const intptr_t *) ptr2; + return cx_vcmp_intptr(p1, p2); +} + +int cx_vcmp_uintptr(uintptr_t p1, uintptr_t p2) { if (p1 == p2) { return 0; } else { @@ -192,11 +260,7 @@ ) { uintptr_t p1 = *(const uintptr_t *) ptr1; uintptr_t p2 = *(const uintptr_t *) ptr2; - if (p1 == p2) { - return 0; - } else { - return p1 < p2 ? -1 : 1; - } + return cx_vcmp_uintptr(p1, p2); } int cx_cmp_ptr( diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/allocator.h --- a/ucx/cx/allocator.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/allocator.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file allocator.h + * @file allocator.h * Interface for custom allocators. */ @@ -54,7 +54,6 @@ /** * The allocator's realloc() implementation. */ - __attribute__((__warn_unused_result__)) void *(*realloc)( void *data, void *mem, @@ -73,7 +72,6 @@ /** * The allocator's free() implementation. */ - __attribute__((__nonnull__)) void (*free)( void *data, void *mem @@ -108,76 +106,157 @@ * Function pointer type for destructor functions. * * A destructor function deallocates possible contents and MAY free the memory - * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param memory a pointer to the object to destruct */ -__attribute__((__nonnull__)) typedef void (*cx_destructor_func)(void *memory); /** * Function pointer type for destructor functions. * * A destructor function deallocates possible contents and MAY free the memory - * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param data an optional pointer to custom data * @param memory a pointer to the object to destruct */ -__attribute__((__nonnull__(2))) typedef void (*cx_destructor_func2)( void *data, void *memory ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * - * \par Error handling - * \c errno will be set by realloc() on failure. + * @par Error handling + * @c errno will be set by realloc() on failure. * * @param mem pointer to the pointer to allocated block * @param n the new size in bytes - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cx_reallocate( void **mem, size_t n ); /** - * Allocate \p n bytes of memory. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero failure + * @see cx_reallocate() + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_reallocatearray( + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * @par Error handling + * @c errno will be set by realloc() on failure. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() + */ +#define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n) + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cx_reallocatearray(mem, nmemb, size) \ + cx_reallocatearray((void**)(mem), nmemb, size) + +/** + * Free a block allocated by this allocator. + * + * @note Freeing a block of a different allocator is undefined. + * + * @param allocator the allocator + * @param mem a pointer to the block to free + */ +cx_attr_nonnull_arg(1) +void cxFree( + const CxAllocator *allocator, + void *mem +); + +/** + * Allocate @p n bytes of memory. * * @param allocator the allocator * @param n the number of bytes * @return a pointer to the allocated memory */ -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2))) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) void *cxMalloc( const CxAllocator *allocator, size_t n ); /** - * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long. - * This function may return the same pointer that was passed to it, if moving the memory - * was not necessary. + * Re-allocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. * - * \note Re-allocating a block allocated by a different allocator is undefined. + * @note Re-allocating a block allocated by a different allocator is undefined. * * @param allocator the allocator * @param mem pointer to the previously allocated block * @param n the new size in bytes * @return a pointer to the re-allocated memory */ -__attribute__((__warn_unused_result__)) -__attribute__((__alloc_size__(3))) +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3) void *cxRealloc( const CxAllocator *allocator, void *mem, @@ -185,20 +264,52 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. - * This function acts like cxRealloc() using the pointer pointed to by \p mem. + * Re-allocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * If that multiplication overflows, this function returns @c NULL and @c errno + * will be set. + * + * @note Re-allocating a block allocated by a different allocator is undefined. * - * \note Re-allocating a block allocated by a different allocator is undefined. + * @param allocator the allocator + * @param mem pointer to the previously allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @return a pointer to the re-allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3, 4) +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. * - * \par Error handling - * \c errno will be set, if the underlying realloc function does so. + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. * * @param allocator the allocator * @param mem pointer to the pointer to allocated block * @param n the new size in bytes - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cxReallocate( const CxAllocator *allocator, void **mem, @@ -206,35 +317,93 @@ ); /** - * Allocate \p nelem elements of \p n bytes each, all initialized to zero. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocate(allocator, mem, n) \ + cxReallocate(allocator, (void**)(mem), n) + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param allocator the allocator + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero on failure + */ +cx_attr_nodiscard +cx_attr_nonnull +int cxReallocateArray( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocateArray(allocator, mem, nmemb, size) \ + cxReallocateArray(allocator, (void**) (mem), nmemb, size) + +/** + * Allocate @p nelem elements of @p n bytes each, all initialized to zero. * * @param allocator the allocator * @param nelem the number of elements * @param n the size of each element in bytes * @return a pointer to the allocated memory */ -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2, 3))) +cx_attr_nonnull_arg(1) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2, 3) void *cxCalloc( const CxAllocator *allocator, size_t nelem, size_t n ); -/** - * Free a block allocated by this allocator. - * - * \note Freeing a block of a different allocator is undefined. - * - * @param allocator the allocator - * @param mem a pointer to the block to free - */ -__attribute__((__nonnull__)) -void cxFree( - const CxAllocator *allocator, - void *mem -); - #ifdef __cplusplus } // extern "C" #endif diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/array_list.h --- a/ucx/cx/array_list.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/array_list.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file array_list.h - * \brief Array list implementation. - * \details Also provides several low-level functions for custom array list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file array_list.h + * @brief Array list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ @@ -45,30 +44,90 @@ #endif /** - * The maximum item size in an array list that fits into stack buffer when swapped. + * The maximum item size in an array list that fits into stack buffer + * when swapped. */ -extern unsigned cx_array_swap_sbo_size; +extern const unsigned cx_array_swap_sbo_size; + +/** + * Declares variables for an array that can be used with the convenience macros. + * + * @par Examples + * @code + * // integer array with at most 255 elements + * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t) + * + * // array of MyObject* pointers where size and capacity are stored as unsigned int + * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int) + * + * // initializing code + * cx_array_initialize(myarray, 16); // reserve space for 16 + * cx_array_initialize(objects, 100); // reserve space for 100 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t) + * + * @see cx_array_initialize() + * @see cx_array_simple_add() + * @see cx_array_simple_copy() + * @see cx_array_simple_add_sorted() + * @see cx_array_simple_insert_sorted() + */ +#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \ + type * name; \ + /** Array size. */ size_type name##_size; \ + /** Array capacity. */ size_type name##_capacity /** * Declares variables for an array that can be used with the convenience macros. * + * The size and capacity variables will have @c size_t type. + * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. + * + * @par Examples + * @code + * // int array + * CX_ARRAY_DECLARE(int, myarray) + * + * // initializing code + * cx_array_initialize(myarray, 32); // reserve space for 32 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * + * @see cx_array_initialize() * @see cx_array_simple_add() * @see cx_array_simple_copy() - * @see cx_array_initialize() * @see cx_array_simple_add_sorted() * @see cx_array_simple_insert_sorted() */ -#define CX_ARRAY_DECLARE(type, name) \ - type * name; \ - size_t name##_size; \ - size_t name##_capacity +#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t) /** - * Initializes an array declared with CX_ARRAY_DECLARE(). + * Initializes an array with the given capacity. + * + * The type of the capacity depends on the type used during declaration. + * + * @par Examples + * @code + * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t) + * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t + * + * // initializing code + * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255 + * cx_array_initialize(arr2, 500); // OK + * @endcode + * * * The memory for the array is allocated with stdlib malloc(). - * @param array the array + * @param array the name of the array * @param capacity the initial capacity + * @see cx_array_initialize_a() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() */ #define cx_array_initialize(array, capacity) \ array##_capacity = capacity; \ @@ -76,7 +135,36 @@ array = malloc(sizeof(array[0]) * capacity) /** + * Initializes an array with the given capacity using the specified allocator. + * + * @par Example + * @code + * CX_ARRAY_DECLARE(int, myarray) + * + * + * const CxAllocator *al = // ... + * cx_array_initialize_a(al, myarray, 128); + * // ... + * cxFree(al, myarray); // don't forget to free with same allocator + * @endcode + * + * The memory for the array is allocated with stdlib malloc(). + * @param allocator (@c CxAllocator*) the allocator + * @param array the name of the array + * @param capacity the initial capacity + * @see cx_array_initialize() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() + */ +#define cx_array_initialize_a(allocator, array, capacity) \ + array##_capacity = capacity; \ + array##_size = 0; \ + array = cxMalloc(allocator, sizeof(array[0]) * capacity) + +/** * Defines a reallocation mechanism for arrays. + * You can create your own, use cx_array_reallocator(), or + * use the #cx_array_default_reallocator. */ struct cx_array_reallocator_s { /** @@ -92,8 +180,11 @@ * @param capacity the new capacity (number of elements) * @param elem_size the size of each element * @param alloc a reference to this allocator - * @return a pointer to the reallocated memory or \c NULL on failure + * @return a pointer to the reallocated memory or @c NULL on failure */ + cx_attr_nodiscard + cx_attr_nonnull_arg(4) + cx_attr_allocsize(2, 3) void *(*realloc)( void *array, size_t capacity, @@ -120,125 +211,271 @@ }; /** + * Typedef for the array reallocator struct. + */ +typedef struct cx_array_reallocator_s CxArrayReallocator; + +/** * A default stdlib-based array reallocator. */ -extern struct cx_array_reallocator_s *cx_array_default_reallocator; +extern CxArrayReallocator *cx_array_default_reallocator; + +/** + * Creates a new array reallocator. + * + * When @p allocator is @c NULL, the stdlib default allocator will be used. + * + * When @p stackmem is not @c NULL, the reallocator is supposed to be used + * @em only for the specific array that is initially located at @p stackmem. + * When reallocation is needed, the reallocator checks, if the array is + * still located at @p stackmem and copies the contents to the heap. + * + * @note Invoking this function with both arguments @c NULL will return a + * reallocator that behaves like #cx_array_default_reallocator. + * + * @param allocator the allocator this reallocator shall be based on + * @param stackmem the address of the array when the array is initially located + * on the stack or shall not reallocated in place + * @return an array reallocator + */ +CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +); /** - * Return codes for array functions. + * Reserves memory for additional elements. + * + * This function checks if the @p capacity of the array is sufficient to hold + * at least @p size plus @p elem_count elements. If not, a reallocation is + * performed with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * This function can be useful to replace subsequent calls to cx_array_copy() + * with one single cx_array_reserve() and then - after guaranteeing a + * sufficient capacity - use simple memmove() or memcpy(). + * + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. + * + * @param array a pointer to the target array + * @param size a pointer to the size of the array + * @param capacity a pointer to the capacity of the array + * @param width the width in bytes for the @p size and @p capacity or zero for default + * @param elem_size the size of one element + * @param elem_count the number of expected additional elements + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() */ -enum cx_array_result { - CX_ARRAY_SUCCESS, - CX_ARRAY_REALLOC_NOT_SUPPORTED, - CX_ARRAY_REALLOC_FAILED, -}; +cx_attr_nonnull_arg(1, 2, 3) +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); /** * Copies elements from one array to another. * - * The elements are copied to the \p target array at the specified \p index, - * overwriting possible elements. The \p index does not need to be in range of - * the current array \p size. If the new index plus the number of elements added - * would extend the array's size, and \p capacity is not \c NULL, the remaining - * capacity is used. + * The elements are copied to the @p target array at the specified @p index, + * overwriting possible elements. The @p index does not need to be in range of + * the current array @p size. If the new index plus the number of elements added + * would extend the array's size, the remaining @p capacity is used. * - * If the capacity is insufficient to hold the new data, a reallocation - * attempt is made, unless the \p reallocator is set to \c NULL, in which case - * this function ultimately returns a failure. + * If the @p capacity is also insufficient to hold the new data, a reallocation + * attempt is made with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. * * @param target a pointer to the target array * @param size a pointer to the size of the target array - * @param capacity a pointer to the target array's capacity - - * \c NULL if only the size shall be used to bound the array (reallocations - * will NOT be supported in that case) + * @param capacity a pointer to the capacity of the target array + * @param width the width in bytes for the @p size and @p capacity or zero for default * @param index the index where the copied elements shall be placed * @param src the source array * @param elem_size the size of one element * @param elem_count the number of elements to copy - * @param reallocator the array reallocator to use, or \c NULL - * if reallocation shall not happen - * @return zero on success, non-zero error code on failure + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() */ -__attribute__((__nonnull__(1, 2, 5))) -enum cx_array_result cx_array_copy( +cx_attr_nonnull_arg(1, 2, 3, 6) +int cx_array_copy( void **target, - size_t *size, - size_t *capacity, + void *size, + void *capacity, + unsigned width, size_t index, const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ); /** - * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator. + * Convenience macro that uses cx_array_copy() with a default layout and + * the specified reallocator. * - * @param array the name of the array (NOT a pointer to the array) - * @param index the index where the copied elements shall be placed - * @param src the source array - * @param count the number of elements to copy + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy() + */ +#define cx_array_simple_copy_a(reallocator, array, index, src, count) \ + cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), index, src, sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_copy() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy_a() */ #define cx_array_simple_copy(array, index, src, count) \ - cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \ - index, src, sizeof((array)[0]), count, cx_array_default_reallocator) + cx_array_simple_copy_a(NULL, array, index, src, count) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected @em additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve() + */ +#define cx_array_simple_reserve_a(reallocator, array, count) \ + cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve_a() + */ +#define cx_array_simple_reserve(array, count) \ + cx_array_simple_reserve_a(NULL, array, count) /** * Adds an element to an array with the possibility of allocating more space. * - * The element \p elem is added to the end of the \p target array which containing - * \p size elements, already. The \p capacity must not be \c NULL and point a - * variable holding the current maximum number of elements the array can hold. + * The element @p elem is added to the end of the @p target array which contains + * @p size elements, already. The @p capacity must point to a variable denoting + * the current maximum number of elements the array can hold. * - * If the capacity is insufficient to hold the new element, and the optional - * \p reallocator is not \c NULL, an attempt increase the \p capacity is made - * and the new capacity is written back. + * If the capacity is insufficient to hold the new element, an attempt to + * increase the @p capacity is made and the new capacity is written back. * - * @param target a pointer to the target array - * @param size a pointer to the size of the target array - * @param capacity a pointer to the target array's capacity - must not be \c NULL - * @param elem_size the size of one element - * @param elem a pointer to the element to add - * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen - * @return zero on success, non-zero error code on failure + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure */ #define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \ - cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator) + cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \ + *(size), elem, elem_size, 1, reallocator) + +/** + * Convenience macro that uses cx_array_add() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add() + */ +#define cx_array_simple_add_a(reallocator, array, elem) \ + cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1) /** * Convenience macro that uses cx_array_add() with a default layout and * the default reallocator. * - * @param array the name of the array (NOT a pointer to the array) + * @param array the name of the array (NOT a pointer or alias to the array) * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_a() */ #define cx_array_simple_add(array, elem) \ - cx_array_simple_copy(array, array##_size, &(elem), 1) - + cx_array_simple_add_a(cx_array_default_reallocator, array, elem) /** * Inserts a sorted array into another sorted array. * * If either the target or the source array is not already sorted with respect - * to the specified \p cmp_func, the behavior is undefined. + * to the specified @p cmp_func, the behavior is undefined. * * If the capacity is insufficient to hold the new data, a reallocation * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. * * @param target a pointer to the target array * @param size a pointer to the size of the target array - * @param capacity a pointer to the target array's capacity + * @param capacity a pointer to the capacity of the target array * @param cmp_func the compare function for the elements * @param src the source array * @param elem_size the size of one element * @param elem_count the number of elements to insert * @param reallocator the array reallocator to use - * @return zero on success, non-zero error code on failure + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) -enum cx_array_result cx_array_insert_sorted( +cx_attr_nonnull_arg(1, 2, 3, 5) +int cx_array_insert_sorted( void **target, size_t *size, size_t *capacity, @@ -246,68 +483,112 @@ const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ); /** * Inserts an element into a sorted array. * * If the target array is not already sorted with respect - * to the specified \p cmp_func, the behavior is undefined. + * to the specified @p cmp_func, the behavior is undefined. * * If the capacity is insufficient to hold the new data, a reallocation * attempt is made. * - * @param target a pointer to the target array - * @param size a pointer to the size of the target array - * @param capacity a pointer to the target array's capacity - * @param elem_size the size of one element - * @param elem a pointer to the element to add - * @param reallocator the array reallocator to use - * @return zero on success, non-zero error code on failure + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure */ #define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) /** * Convenience macro for cx_array_add_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted() + */ +#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \ + cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_sorted() with a default * layout and the default reallocator. * - * @param array the name of the array (NOT a pointer to the array) + * @param array the name of the array (NOT a pointer or alias to the array) * @param elem the element to add (NOT a pointer, address is automatically taken) - * @param cmp_func the compare function for the elements + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted_a() */ #define cx_array_simple_add_sorted(array, elem, cmp_func) \ - cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \ - sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator) + cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted() + */ +#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) /** * Convenience macro for cx_array_insert_sorted() with a default * layout and the default reallocator. * - * @param array the name of the array (NOT a pointer to the array) - * @param src pointer to the source array - * @param n number of elements in the source array - * @param cmp_func the compare function for the elements + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted_a() */ #define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ - cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ - cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator) - + cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) /** * Searches the largest lower bound in a sorted array. * * In other words, this function returns the index of the largest element - * in \p arr that is less or equal to \p elem with respect to \p cmp_func. - * When no such element exists, \p size is returned. + * in @p arr that is less or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. * - * If \p elem is contained in the array, this is identical to + * If @p elem is contained in the array, this is identical to * #cx_array_binary_search(). * - * If the array is not sorted with respect to the \p cmp_func, the behavior + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -315,9 +596,11 @@ * @param elem_size the size of one element * @param elem the element to find * @param cmp_func the compare function - * @return the index of the largest lower bound, or \p size + * @return the index of the largest lower bound, or @p size + * @see cx_array_binary_search_sup() + * @see cx_array_binary_search() */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -329,7 +612,7 @@ /** * Searches an item in a sorted array. * - * If the array is not sorted with respect to the \p cmp_func, the behavior + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -337,38 +620,31 @@ * @param elem_size the size of one element * @param elem the element to find * @param cmp_func the compare function - * @return the index of the element in the array, or \p size if the element + * @return the index of the element in the array, or @p size if the element * cannot be found + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search_sup() */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search( +cx_attr_nonnull +size_t cx_array_binary_search( const void *arr, size_t size, size_t elem_size, const void *elem, cx_compare_func cmp_func -) { - size_t index = cx_array_binary_search_inf( - arr, size, elem_size, elem, cmp_func - ); - if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { - return index; - } else { - return size; - } -} +); /** * Searches the smallest upper bound in a sorted array. * * In other words, this function returns the index of the smallest element - * in \p arr that is greater or equal to \p elem with respect to \p cmp_func. - * When no such element exists, \p size is returned. + * in @p arr that is greater or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. * - * If \p elem is contained in the array, this is identical to + * If @p elem is contained in the array, this is identical to * #cx_array_binary_search(). * - * If the array is not sorted with respect to the \p cmp_func, the behavior + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -376,26 +652,18 @@ * @param elem_size the size of one element * @param elem the element to find * @param cmp_func the compare function - * @return the index of the smallest upper bound, or \p size + * @return the index of the smallest upper bound, or @p size + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search() */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search_sup( +cx_attr_nonnull +size_t cx_array_binary_search_sup( const void *arr, size_t size, size_t elem_size, const void *elem, cx_compare_func cmp_func -) { - size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func); - if (inf == size) { - // no infimum means, first element is supremum - return 0; - } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { - return inf; - } else { - return inf + 1; - } -} +); /** * Swaps two array elements. @@ -405,7 +673,7 @@ * @param idx1 index of first element * @param idx2 index of second element */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_array_swap( void *arr, size_t elem_size, @@ -414,21 +682,24 @@ ); /** - * Allocates an array list for storing elements with \p elem_size bytes each. + * Allocates an array list for storing elements with @p elem_size bytes each. * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list memory - * (if \c NULL the cxDefaultAllocator will be used) + * (if @c NULL, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL, and the list is not storing pointers, sort and find + * (if @c NULL, and the list is not storing pointers, sort and find * functions will not work) * @param elem_size the size of each element in bytes * @param initial_capacity the initial number of elements the array can store * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) CxList *cxArrayListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -437,18 +708,18 @@ ); /** - * Allocates an array list for storing elements with \p elem_size bytes each. + * Allocates an array list for storing elements with @p elem_size bytes each. * - * The list will use the cxDefaultAllocator and \em NO compare function. + * The list will use the cxDefaultAllocator and @em NO compare function. * If you want to call functions that need a compare function, you have to * set it immediately after creation or use cxArrayListCreate(). * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(). * - * @param elem_size the size of each element in bytes - * @param initial_capacity the initial number of elements the array can store + * @param elem_size (@c size_t) the size of each element in bytes + * @param initial_capacity (@c size_t) the initial number of elements the array can store * @return the created list */ #define cxArrayListCreateSimple(elem_size, initial_capacity) \ diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/buffer.h --- a/ucx/cx/buffer.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/buffer.h Mon Jan 06 22:22:55 2025 +0100 @@ -27,9 +27,9 @@ */ /** - * \file buffer.h + * @file buffer.h * - * \brief Advanced buffer implementation. + * @brief Advanced buffer implementation. * * Instances of CxBuffer can be used to read from or to write to like one * would do with a stream. @@ -38,9 +38,9 @@ * can be enabled. See the documentation of the macro constants for more * information. * - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_BUFFER_H @@ -49,7 +49,7 @@ #include "common.h" #include "allocator.h" -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -60,16 +60,90 @@ /** * If this flag is enabled, the buffer will automatically free its contents when destroyed. + * + * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically + * set when the copy-on-write operations is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 /** - * If this flag is enabled, the buffer will automatically extends its capacity. + * If this flag is enabled, the buffer will automatically extend its capacity. */ #define CX_BUFFER_AUTO_EXTEND 0x02 +/** + * If this flag is enabled, the buffer will allocate new memory when written to. + * + * The current contents of the buffer will be copied to the new memory and the flag + * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. + */ +#define CX_BUFFER_COPY_ON_WRITE 0x04 + +/** + * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation. + * + * After performing the copy, the flag is automatically cleared. + * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why + * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled. + */ +#define CX_BUFFER_COPY_ON_EXTEND 0x08 + +/** + * Configuration for automatic flushing. + */ +struct cx_buffer_flush_config_s { + /** + * The buffer may not extend beyond this threshold before starting to flush. + * + * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. + * The threshold will be the maximum capacity the buffer is extended to + * before flushing. + */ + size_t threshold; + /** + * The block size for the elements to flush. + */ + size_t blksize; + /** + * The maximum number of blocks to flush in one cycle. + * + * @attention while it is guaranteed that cxBufferFlush() will not flush + * more blocks, this is not necessarily the case for cxBufferWrite(). + * After performing a flush cycle, cxBufferWrite() will retry the write + * operation and potentially trigger another flush cycle, until the + * flush target accepts no more data. + */ + size_t blkmax; + + /** + * The target for write function. + */ + void *target; + + /** + * The write-function used for flushing. + * If NULL, the flushed content gets discarded. + */ + cx_write_func wfunc; +}; + +/** + * Type alais for the flush configuration struct. + * + * @code + * struct cx_buffer_flush_config_s { + * size_t threshold; + * size_t blksize; + * size_t blkmax; + * void *target; + * cx_write_func wfunc; + * }; + * @endcode + */ +typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; + /** Structure for the UCX buffer data. */ -typedef struct { +struct cx_buffer_s { /** A pointer to the buffer contents. */ union { /** @@ -83,6 +157,12 @@ }; /** The allocator to use for automatic memory management. */ const CxAllocator *allocator; + /** + * Optional flush configuration + * + * @see cxBufferEnableFlushing() + */ + CxBufferFlushConfig* flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ @@ -90,70 +170,52 @@ /** Current size of the buffer content. */ size_t size; /** - * The buffer may not extend beyond this threshold before starting to flush. - * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled). - */ - size_t flush_threshold; - /** - * The block size for the elements to flush. - * Default is 4096 bytes. - */ - size_t flush_blksize; - /** - * The maximum number of blocks to flush in one cycle. - * Zero disables flushing entirely (this is the default). - * Set this to \c SIZE_MAX to flush the entire buffer. - * - * @attention if the maximum number of blocks multiplied with the block size - * is smaller than the expected contents written to this buffer within one write - * operation, multiple flush cycles are performed after that write. - * That means the total number of blocks flushed after one write to this buffer may - * be larger than \c flush_blkmax. - */ - size_t flush_blkmax; - - /** - * The write function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func flush_func; - - /** - * The target for \c flush_func. - */ - void *flush_target; - - /** * Flag register for buffer features. * @see #CX_BUFFER_DEFAULT * @see #CX_BUFFER_FREE_CONTENTS * @see #CX_BUFFER_AUTO_EXTEND + * @see #CX_BUFFER_COPY_ON_WRITE */ int flags; -} cx_buffer_s; +}; /** * UCX buffer. */ -typedef cx_buffer_s CxBuffer; +typedef struct cx_buffer_s CxBuffer; /** * Initializes a fresh buffer. * - * \note You may provide \c NULL as argument for \p space. + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * + * You need to set the size manually after initialization, if + * you provide @p space which already contains data. + * + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. + * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying + * copy-on-write should be avoided, because the allocated + * space will be leaking after the copy-on-write operation. * * @param buffer the buffer to initialize - * @param space pointer to the memory area, or \c NULL to allocate + * @param space pointer to the memory area, or @c NULL to allocate * new memory * @param capacity the capacity of the buffer * @param allocator the allocator this buffer shall use for automatic - * memory management. If \c NULL, the default heap allocator will be used. + * memory management + * (if @c NULL, a default stdlib allocator will be used) * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) int cxBufferInit( CxBuffer *buffer, void *space, @@ -163,25 +225,23 @@ ); /** - * Allocates and initializes a fresh buffer. + * Configures the buffer for flushing. * - * \note You may provide \c NULL as argument for \p space. - * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. + * Flushing can happen automatically when data is written + * to the buffer (see cxBufferWrite()) or manually when + * cxBufferFlush() is called. * - * @param space pointer to the memory area, or \c NULL to allocate - * new memory - * @param capacity the capacity of the buffer - * @param allocator the allocator to use for allocating the structure and the automatic - * memory management within the buffer. If \c NULL, the default heap allocator will be used. - * @param flags buffer features (see cx_buffer_s.flags) - * @return a pointer to the buffer on success, \c NULL if a required allocation failed + * @param buffer the buffer + * @param config the flush configuration + * @retval zero success + * @retval non-zero failure + * @see cxBufferFlush() + * @see cxBufferWrite() */ -CxBuffer *cxBufferCreate( - void *space, - size_t capacity, - const CxAllocator *allocator, - int flags +cx_attr_nonnull +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config ); /** @@ -193,22 +253,58 @@ * @param buffer the buffer which contents shall be destroyed * @see cxBufferInit() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferDestroy(CxBuffer *buffer); /** * Deallocates the buffer. * * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys - * the contents. If you \em only want to destroy the contents, use cxBufferDestroy(). + * the contents. If you @em only want to destroy the contents, use cxBufferDestroy(). + * + * @remark As with all free() functions, this accepts @c NULL arguments in which + * case it does nothing. * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ -__attribute__((__nonnull__)) void cxBufferFree(CxBuffer *buffer); /** + * Allocates and initializes a fresh buffer. + * + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. + * Then this function will allocate the space and enforce + * the #CX_BUFFER_FREE_CONTENTS flag. + * + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param allocator the allocator to use for allocating the structure and the automatic + * memory management within the buffer + * (if @c NULL, a default stdlib allocator will be used) + * @param flags buffer features (see cx_buffer_s.flags) + * @return a pointer to the buffer on success, @c NULL if a required allocation failed + */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +); + +/** * Shifts the contents of the buffer by the given offset. * * If the offset is positive, the contents are shifted to the right. @@ -219,7 +315,7 @@ * are discarded. * * If the offset is negative, the contents are shifted to the left where the - * first \p shift bytes are discarded. + * first @p shift bytes are discarded. * The new size of the buffer is the old size minus the absolute shift value. * If this value is larger than the buffer size, the buffer is emptied (but * not cleared, see the security note below). @@ -227,11 +323,11 @@ * The buffer position gets shifted alongside with the content but is kept * within the boundaries of the buffer. * - * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and - * cxBufferShiftRight() functions using a \c size_t as parameter type. + * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and + * cxBufferShiftRight() functions using a @c size_t as parameter type. * - * \attention - * Security Note: The shifting operation does \em not erase the previously occupied memory cells. + * @attention + * Security Note: The shifting operation does @em not erase the previously occupied memory cells. * But you can easily do that manually, e.g. by calling * memset(buffer->bytes, 0, shift) for a right shift or * memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size) @@ -239,9 +335,12 @@ * * @param buffer the buffer * @param shift the shift offset (negative means left shift) - * @return 0 on success, non-zero if a required auto-extension fails + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails + * @see cxBufferShiftLeft() + * @see cxBufferShiftRight() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShift( CxBuffer *buffer, off_t shift @@ -253,10 +352,11 @@ * * @param buffer the buffer * @param shift the shift offset - * @return 0 on success, non-zero if a required auto-extension fails + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -266,15 +366,13 @@ * Shifts the buffer to the left. * See cxBufferShift() for details. * - * \note Since a left shift cannot fail due to memory allocation problems, this - * function always returns zero. - * * @param buffer the buffer * @param shift the positive shift offset - * @return always zero + * @retval zero success + * @retval non-zero if the buffer uses copy-on-write and the allocation fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -284,23 +382,24 @@ /** * Moves the position of the buffer. * - * The new position is relative to the \p whence argument. + * The new position is relative to the @p whence argument. * - * \li \c SEEK_SET marks the start of the buffer. - * \li \c SEEK_CUR marks the current position. - * \li \c SEEK_END marks the end of the buffer. + * @li @c SEEK_SET marks the start of the buffer. + * @li @c SEEK_CUR marks the current position. + * @li @c SEEK_END marks the end of the buffer. * * With an offset of zero, this function sets the buffer position to zero - * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position - * unchanged (\c SEEK_CUR). + * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position + * unchanged (@c SEEK_CUR). * * @param buffer the buffer - * @param offset position offset relative to \p whence - * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END - * @return 0 on success, non-zero if the position is invalid + * @param offset position offset relative to @p whence + * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END + * @retval zero success + * @retval non-zero if the position is invalid * */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -313,10 +412,13 @@ * The data is deleted by zeroing it with a call to memset(). * If you do not need that, you can use the faster cxBufferReset(). * + * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function + * will not erase the data and behave exactly as cxBufferReset(). + * * @param buffer the buffer to be cleared * @see cxBufferReset() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferClear(CxBuffer *buffer); /** @@ -328,18 +430,20 @@ * @param buffer the buffer to be cleared * @see cxBufferClear() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferReset(CxBuffer *buffer); /** * Tests, if the buffer position has exceeded the buffer size. * * @param buffer the buffer to test - * @return non-zero, if the current buffer position has exceeded the last - * byte of the buffer's contents. + * @retval true if the current buffer position has exceeded the last + * byte of the buffer's contents + * @retval false otherwise */ -__attribute__((__nonnull__)) -int cxBufferEof(const CxBuffer *buffer); +cx_attr_nonnull +cx_attr_nodiscard +bool cxBufferEof(const CxBuffer *buffer); /** @@ -349,9 +453,10 @@ * * @param buffer the buffer * @param capacity the minimum required capacity for this buffer - * @return 0 on success or a non-zero value on failure + * @retval zero the capacity was already sufficient or successfully increased + * @retval non-zero on allocation failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -360,6 +465,10 @@ /** * Writes data to a CxBuffer. * + * If automatic flushing is not enabled, the data is simply written into the + * buffer at the current position and the position of the buffer is increased + * by the number of bytes written. + * * If flushing is enabled and the buffer needs to flush, the data is flushed to * the target until the target signals that it cannot take more data by * returning zero via the respective write function. In that case, the remaining @@ -367,23 +476,23 @@ * newly available space can be used to append as much data as possible. This * function only stops writing more elements, when the flush target and this * buffer are both incapable of taking more data or all data has been written. - * The number returned by this function is the total number of elements that - * could be written during the process. It does not necessarily mean that those - * elements are still in this buffer, because some of them could have also be - * flushed already. + * If number of items that shall be written is larger than the buffer can hold, + * the first items from @c ptr are directly relayed to the flush target, if + * possible. + * The number returned by this function is only the number of elements from + * @c ptr that could be written to either the flush target or the buffer. * - * If automatic flushing is not enabled, the position of the buffer is increased - * by the number of bytes written. - * - * \note The signature is compatible with the fwrite() family of functions. + * @note The signature is compatible with the fwrite() family of functions. * * @param ptr a pointer to the memory area containing the bytes to be written * @param size the length of one element * @param nitems the element count * @param buffer the CxBuffer to write to * @return the total count of elements written + * @see cxBufferAppend() + * @see cxBufferRead() */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferWrite( const void *ptr, size_t size, @@ -392,19 +501,104 @@ ); /** + * Appends data to a CxBuffer. + * + * The data is always appended to current data within the buffer, + * regardless of the current position. + * This is especially useful when the buffer is primarily meant for reading + * while additional data is added to the buffer occasionally. + * Consequently, the position of the buffer is unchanged after this operation. + * + * @note The signature is compatible with the fwrite() family of functions. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to write to + * @return the total count of elements written + * @see cxBufferWrite() + * @see cxBufferRead() + */ +cx_attr_nonnull +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Performs a single flush-run on the specified buffer. + * + * Does nothing when the position in the buffer is zero. + * Otherwise, the data until the current position minus + * one is considered for flushing. + * Note carefully that flushing will never exceed the + * current @em position, even when the size of the + * buffer is larger than the current position. + * + * One flush run will try to flush @c blkmax many + * blocks of size @c blksize until either the @p buffer + * has no more data to flush or the write function + * used for flushing returns zero. + * + * The buffer is shifted left for that many bytes + * the flush operation has successfully flushed. + * + * @par Example 1 + * Assume you have a buffer with size 340 and you are + * at position 200. The flush configuration is + * @c blkmax=4 and @c blksize=64 . + * Assume that the entire flush operation is successful. + * All 200 bytes on the left hand-side from the current + * position are written. + * That means, the size of the buffer is now 140 and the + * position is zero. + * + * @par Example 2 + * Same as Example 1, but now the @c blkmax is 1. + * The size of the buffer is now 276 and the position is 136. + * + * @par Example 3 + * Same as Example 1, but now assume the flush target + * only accepts 100 bytes before returning zero. + * That means, the flush operations manages to flush + * one complete block and one partial block, ending + * up with a buffer with size 240 and position 100. + * + * @remark Just returns zero when flushing was not enabled with + * cxBufferEnableFlushing(). + * + * @remark When the buffer uses copy-on-write, the memory + * is copied first, before attempting any flush. + * This is, however, considered an erroneous use of the + * buffer, because it does not make much sense to put + * readonly data into an UCX buffer for flushing, instead + * of writing it directly to the target. + * + * @param buffer the buffer + * @return the number of successfully flushed bytes + * @see cxBufferEnableFlushing() + */ +cx_attr_nonnull +size_t cxBufferFlush(CxBuffer *buffer); + +/** * Reads data from a CxBuffer. * * The position of the buffer is increased by the number of bytes read. * - * \note The signature is compatible with the fread() family of functions. + * @note The signature is compatible with the fread() family of functions. * * @param ptr a pointer to the memory area where to store the read data * @param size the length of one element * @param nitems the element count * @param buffer the CxBuffer to read from * @return the total number of elements read + * @see cxBufferWrite() + * @see cxBufferAppend() */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferRead( void *ptr, size_t size, @@ -417,30 +611,52 @@ * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is - * disabled or buffer extension fails, \c EOF is returned. + * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature + * is disabled or buffer extension fails, @c EOF is returned. * * On successful write, the position of the buffer is increased. * + * If you just want to write a null-terminator at the current position, you + * should use cxBufferTerminate() instead. + * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has bean written or \c EOF when the end of the stream is + * @return the byte that has been written or @c EOF when the end of the stream is * reached and automatic extension is not enabled or not possible + * @see cxBufferTerminate() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferPut( CxBuffer *buffer, int c ); /** + * Writes a terminating zero to a buffer at the current position. + * + * On successful write, @em neither the position @em nor the size of the buffer is + * increased. + * + * The purpose of this function is to have the written data ready to be used as + * a C string. + * + * @param buffer the buffer to write to + * @return zero, if the terminator could be written, non-zero otherwise + */ +cx_attr_nonnull +int cxBufferTerminate(CxBuffer *buffer); + +/** * Writes a string to a buffer. * + * This is a convenience function for cxBufferWrite(str, 1, strlen(str), buffer). + * * @param buffer the buffer * @param str the zero-terminated string * @return the number of bytes written */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -452,9 +668,9 @@ * The current position of the buffer is increased after a successful read. * * @param buffer the buffer to read from - * @return the character or \c EOF, if the end of the buffer is reached + * @return the character or @c EOF, if the end of the buffer is reached */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/collection.h --- a/ucx/cx/collection.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/collection.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file collection.h - * \brief Common definitions for various collection implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file collection.h + * @brief Common definitions for various collection implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_COLLECTION_H @@ -96,13 +96,21 @@ /** * Use this macro to declare common members for a collection structure. + * + * @par Example Use + * @code + * struct MyCustomSet { + * CX_COLLECTION_BASE; + * MySetElements *data; + * } + * @endcode */ #define CX_COLLECTION_BASE struct cx_collection_s collection /** * Sets a simple destructor function for this collection. * - * @param c the collection + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @param destr the destructor function */ #define cxDefineDestructor(c, destr) \ @@ -111,7 +119,7 @@ /** * Sets a simple destructor function for this collection. * - * @param c the collection + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @param destr the destructor function */ #define cxDefineAdvancedDestructor(c, destr, data) \ @@ -124,8 +132,11 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_simple_destructor(c, e) \ (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e)) @@ -136,8 +147,11 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_advanced_destructor(c, e) \ (c)->collection.advanced_destructor((c)->collection.destructor_data, \ @@ -150,8 +164,11 @@ * Usually only used by collection implementations. There should be no need * to invoke this macro manually. * - * @param c the collection - * @param e the element + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) */ #define cx_invoke_destructor(c, e) \ if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \ diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/common.h --- a/ucx/cx/common.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/common.h Mon Jan 06 22:22:55 2025 +0100 @@ -27,15 +27,15 @@ */ /** - * \file common.h + * @file common.h * - * \brief Common definitions and feature checks. + * @brief Common definitions and feature checks. * - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License * - * \mainpage UAP Common Extensions + * @mainpage UAP Common Extensions * Library with common and useful functions, macros and data structures. *

* Latest available source:
@@ -88,7 +88,9 @@ /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) -// Common Includes +// --------------------------------------------------------------------------- +// Common includes +// --------------------------------------------------------------------------- #include #include @@ -96,7 +98,194 @@ #include #include -#ifndef UCX_TEST_H +// --------------------------------------------------------------------------- +// Architecture Detection +// --------------------------------------------------------------------------- + +#ifndef INTPTR_MAX +#error Missing INTPTR_MAX definition +#endif +#if INTPTR_MAX == INT64_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 64 +#elif INTPTR_MAX == INT32_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 32 +#else +#error Unknown pointer size or missing size macros! +#endif + +// --------------------------------------------------------------------------- +// Missing Defines +// --------------------------------------------------------------------------- + +#ifndef SSIZE_MAX // not defined in glibc since C23 and MSVC +#if CX_WORDSIZE == 64 +/** + * The maximum representable value in ssize_t. + */ +#define SSIZE_MAX 0x7fffffffffffffffll +#else +#define SSIZE_MAX 0x7fffffffl +#endif +#endif + + +// --------------------------------------------------------------------------- +// Attribute definitions +// --------------------------------------------------------------------------- + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +/** + * All pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull __attribute__((__nonnull__)) + +/** + * The specified pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__))) + +/** + * The returned value is guaranteed to be non-NULL. + */ +#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__)) + +/** + * The attributed function always returns freshly allocated memory. + */ +#define cx_attr_malloc __attribute__((__malloc__)) + +#ifndef __clang__ +/** + * The pointer returned by the attributed function is supposed to be freed + * by @p freefunc. + * + * @param freefunc the function that shall be used to free the memory + * @param freefunc_arg the index of the pointer argument in @p freefunc + */ +#define cx_attr_dealloc(freefunc, freefunc_arg) \ + __attribute__((__malloc__(freefunc, freefunc_arg))) +#else +/** + * Not supported in clang. + */ +#define cx_attr_dealloc(...) +#endif // __clang__ + +/** + * Shortcut to specify #cxFree() as deallocator. + */ +#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2) + +/** + * Specifies the parameters from which the allocation size is calculated. + */ +#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__))) + + +#ifdef __clang__ +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +/** + * No support for access attribute in clang. + */ +#define cx_attr_access(mode, ...) +#else +#if __GNUC__ < 10 +/** + * No support for access attribute in GCC < 10. + */ +#define cx_attr_access(mode, ...) +#else +/** + * Helper macro to define access macros. + */ +#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__))) +#endif // __GNUC__ < 10 +#if __GNUC__ < 14 +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +#else +/** + * The specified argument is expected to be a zero-terminated string. + * + * @param idx the index of the argument + */ +#define cx_attr_cstr_arg(idx) \ + __attribute__((__null_terminated_string_arg__(idx))) +#endif // __GNUC__ < 14 +#endif // __clang__ + + +/** + * Specifies that the function will only read through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__) + +/** + * Specifies that the function will read and write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__) + +/** + * Specifies that the function will only write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) + +#if __STDC_VERSION__ >= 202300L + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused [[maybe_unused]] + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard [[nodiscard]] + +#else // no C23 + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused __attribute__((__unused__)) + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard __attribute__((__warn_unused_result__)) + +#endif // __STDC_VERSION__ + +// --------------------------------------------------------------------------- +// Useful function pointers +// --------------------------------------------------------------------------- + /** * Function pointer compatible with fwrite-like functions. */ @@ -106,7 +295,6 @@ size_t, void * ); -#endif // UCX_TEST_H /** * Function pointer compatible with fread-like functions. @@ -118,25 +306,71 @@ void * ); +// --------------------------------------------------------------------------- +// Utility macros +// --------------------------------------------------------------------------- -// Compiler specific stuff +/** + * Determines the number of members in a static C array. + * + * @attention never use this to determine the size of a dynamically allocated + * array. + * + * @param arr the array identifier + * @return the number of elements + */ +#define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0])) -#ifndef __GNUC__ +// --------------------------------------------------------------------------- +// szmul implementation +// --------------------------------------------------------------------------- + +#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) +#define CX_SZMUL_BUILTIN +#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) +#else // no GNUC or clang bultin /** - * Removes GNU C attributes where they are not supported. + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a (@c size_t) first operand + * @param b (@c size_t) second operand + * @param result (@c size_t*) a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow */ -#define __attribute__(x) +#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) + +/** + * Implementation of cx_szmul() when no compiler builtin is available. + * + * Do not use in application code. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow + */ +#if __cplusplus +extern "C" #endif +int cx_szmul_impl(size_t a, size_t b, size_t *result); +#endif // cx_szmul + + +// --------------------------------------------------------------------------- +// Fixes for MSVC incompatibilities +// --------------------------------------------------------------------------- #ifdef _MSC_VER - // fix missing ssize_t definition #include typedef SSIZE_T ssize_t; // fix missing _Thread_local support #define _Thread_local __declspec(thread) - -#endif +#endif // _MSC_VER #endif // UCX_COMMON_H diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/compare.h --- a/ucx/cx/compare.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/compare.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file compare.h - * \brief A collection of simple compare functions. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file compare.h + * @brief A collection of simple compare functions. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_COMPARE_H @@ -42,199 +42,485 @@ extern "C" { #endif -#ifndef CX_COMPARE_FUNC_DEFINED -#define CX_COMPARE_FUNC_DEFINED /** - * A comparator function comparing two collection elements. + * A comparator function comparing two arbitrary values. + * + * All functions from compare.h with the cx_cmp prefix are + * compatible with this signature and can be used as + * compare function for collections, or other implementations + * that need to be type-agnostic. + * + * For simple comparisons the cx_vcmp family of functions + * can be used, but they are NOT compatible with this function + * pointer. */ -typedef int(*cx_compare_func)( - const void *left, - const void *right +cx_attr_nonnull +cx_attr_nodiscard +typedef int (*cx_compare_func)( + const void *left, + const void *right ); -#endif // CX_COMPARE_FUNC_DEFINED /** * Compares two integers of type int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to integer one * @param i2 pointer to integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int(const void *i1, const void *i2); /** + * Compares two ints. + * + * @param i1 integer one + * @param i2 integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int(int i1, int i2); + +/** * Compares two integers of type long int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to long integer one * @param i2 pointer to long integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longint(const void *i1, const void *i2); /** + * Compares two long ints. + * + * @param i1 long integer one + * @param i2 long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_longint(long int i1, long int i2); + +/** * Compares two integers of type long long. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to long long one * @param i2 pointer to long long two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longlong(const void *i1, const void *i2); /** + * Compares twolong long ints. + * + * @param i1 long long int one + * @param i2 long long int two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_longlong(long long int i1, long long int i2); + +/** * Compares two integers of type int16_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to int16_t one * @param i2 pointer to int16_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int16(const void *i1, const void *i2); /** + * Compares two integers of type int16_t. + * + * @param i1 int16_t one + * @param i2 int16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int16(int16_t i1, int16_t i2); + +/** * Compares two integers of type int32_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to int32_t one * @param i2 pointer to int32_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int32(const void *i1, const void *i2); /** + * Compares two integers of type int32_t. + * + * @param i1 int32_t one + * @param i2 int32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int32(int32_t i1, int32_t i2); + +/** * Compares two integers of type int64_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to int64_t one * @param i2 pointer to int64_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int64(const void *i1, const void *i2); /** + * Compares two integers of type int64_t. + * + * @param i1 int64_t one + * @param i2 int64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int64(int64_t i1, int64_t i2); + +/** * Compares two integers of type unsigned int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to unsigned integer one * @param i2 pointer to unsigned integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint(const void *i1, const void *i2); /** + * Compares two unsigned ints. + * + * @param i1 unsigned integer one + * @param i2 unsigned integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint(unsigned int i1, unsigned int i2); + +/** * Compares two integers of type unsigned long int. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to unsigned long integer one * @param i2 pointer to unsigned long integer two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_ulongint(const void *i1, const void *i2); /** + * Compares two unsigned long ints. + * + * @param i1 unsigned long integer one + * @param i2 unsigned long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); + +/** * Compares two integers of type unsigned long long. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to unsigned long long one * @param i2 pointer to unsigned long long two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_ulonglong(const void *i1, const void *i2); /** + * Compares two unsigned long long ints. + * + * @param i1 unsigned long long one + * @param i2 unsigned long long two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); + +/** * Compares two integers of type uint16_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to uint16_t one * @param i2 pointer to uint16_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint16(const void *i1, const void *i2); /** + * Compares two integers of type uint16_t. + * + * @param i1 uint16_t one + * @param i2 uint16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint16(uint16_t i1, uint16_t i2); + +/** * Compares two integers of type uint32_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to uint32_t one * @param i2 pointer to uint32_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint32(const void *i1, const void *i2); /** + * Compares two integers of type uint32_t. + * + * @param i1 uint32_t one + * @param i2 uint32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint32(uint32_t i1, uint32_t i2); + +/** * Compares two integers of type uint64_t. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param i1 pointer to uint64_t one * @param i2 pointer to uint64_t two - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint64(const void *i1, const void *i2); /** + * Compares two integers of type uint64_t. + * + * @param i1 uint64_t one + * @param i2 uint64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint64(uint64_t i1, uint64_t i2); + +/** * Compares two real numbers of type float with precision 1e-6f. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param f1 pointer to float one * @param f2 pointer to float two - * @return -1, if *f1 is less than *f2, 0 if both are equal, - * 1 if *f1 is greater than *f2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_float(const void *f1, const void *f2); -int cx_cmp_float(const void *f1, const void *f2); +/** + * Compares two real numbers of type float with precision 1e-6f. + * + * @param f1 float one + * @param f2 float two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_float(float f1, float f2); /** * Compares two real numbers of type double with precision 1e-14. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param d1 pointer to double one * @param d2 pointer to double two - * @return -1, if *d1 is less than *d2, 0 if both are equal, - * 1 if *d1 is greater than *d2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_double( - const void *d1, - const void *d2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_double(const void *d1, const void *d2); + +/** + * Convenience function + * + * @param d1 double one + * @param d2 double two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_double(double d1, double d2); /** * Compares the integer representation of two pointers. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param ptr1 pointer to pointer one (const intptr_t*) * @param ptr2 pointer to pointer two (const intptr_t*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_intptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_intptr(const void *ptr1, const void *ptr2); + +/** + * Compares the integer representation of two pointers. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** * Compares the unsigned integer representation of two pointers. * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * * @param ptr1 pointer to pointer one (const uintptr_t*) * @param ptr2 pointer to pointer two (const uintptr_t*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_uintptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uintptr(const void *ptr1, const void *ptr2); + +/** + * Compares the unsigned integer representation of two pointers. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); /** * Compares the pointers specified in the arguments without de-referencing. * * @param ptr1 pointer one * @param ptr2 pointer two - * @return -1 if ptr1 is less than ptr2, 0 if both are equal, - * 1 if ptr1 is greater than ptr2 + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument */ -int cx_cmp_ptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C" diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/hash_key.h --- a/ucx/cx/hash_key.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/hash_key.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_key.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file hash_key.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ @@ -38,6 +38,7 @@ #define UCX_HASH_KEY_H #include "common.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -61,15 +62,20 @@ typedef struct cx_hash_key_s CxHashKey; /** - * Computes a murmur2 32 bit hash. + * Computes a murmur2 32-bit hash. * - * You need to initialize \c data and \c len in the key struct. + * You need to initialize @c data and @c len in the key struct. * The hash is then directly written to that struct. * - * \note If \c data is \c NULL, the hash is defined as 1574210520. + * Usually you should not need this function. + * Use cx_hash_key(), instead. + * + * @note If @c data is @c NULL, the hash is defined as 1574210520. * * @param key the key, the hash shall be computed for + * @see cx_hash_key() */ +cx_attr_nonnull void cx_hash_murmur(CxHashKey *key); /** @@ -80,7 +86,8 @@ * @param str the string * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_cstr_arg(1) CxHashKey cx_hash_key_str(const char *str); /** @@ -90,7 +97,8 @@ * @param len the length * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -107,7 +115,8 @@ * @param len the length of object in memory * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) CxHashKey cx_hash_key( const void *obj, size_t len @@ -119,7 +128,18 @@ * @param str the string * @return the hash key */ -#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length) +cx_attr_nodiscard +static inline CxHashKey cx_hash_key_cxstr(cxstring str) { + return cx_hash_key(str.ptr, str.length); +} + +/** + * Computes a hash key from a UCX string. + * + * @param str (@c cxstring or @c cxmutstr) the string + * @return (@c CxHashKey) the hash key + */ +#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) #ifdef __cplusplus } // extern "C" diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/hash_map.h --- a/ucx/cx/hash_map.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/hash_map.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_map.h - * \brief Hash map implementation. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file hash_map.h + * @brief Hash map implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_HASH_MAP_H @@ -67,21 +67,24 @@ /** * Creates a new hash map with the specified number of buckets. * - * If \p buckets is zero, an implementation defined default will be used. + * If @p buckets is zero, an implementation defined default will be used. * - * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if * cxMapStorePointers() was called immediately after creation. * * @note Iterators provided by this hash map implementation provide the remove operation. * The index value of an iterator is incremented when the iterator advanced without removal. - * In other words, when the iterator is finished, \c index==size . + * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use + * (if @c NULL, a default stdlib allocator will be used) * @param itemsize the size of one element * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMapFree, 1) CxMap *cxHashMapCreate( const CxAllocator *allocator, size_t itemsize, @@ -91,23 +94,22 @@ /** * Creates a new hash map with a default number of buckets. * - * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if * cxMapStorePointers() was called immediately after creation. * * @note Iterators provided by this hash map implementation provide the remove operation. * The index value of an iterator is incremented when the iterator advanced without removal. - * In other words, when the iterator is finished, \c index==size . + * In other words, when the iterator is finished, @c index==size . * - * @param itemsize the size of one element - * @return a pointer to the new hash map + * @param itemsize (@c size_t) the size of one element + * @return (@c CxMap*) a pointer to the new hash map */ -#define cxHashMapCreateSimple(itemsize) \ - cxHashMapCreate(cxDefaultAllocator, itemsize, 0) +#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0) /** * Increases the number of buckets, if necessary. * - * The load threshold is \c 0.75*buckets. If the element count exceeds the load + * The load threshold is @c 0.75*buckets. If the element count exceeds the load * threshold, the map will be rehashed. Otherwise, no action is performed and * this function simply returns 0. * @@ -120,9 +122,10 @@ * @note If the specified map is not a hash map, the behavior is undefined. * * @param map the map to rehash - * @return zero on success, non-zero if a memory allocation error occurred + * @retval zero success + * @retval non-zero if a memory allocation error occurred */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxMapRehash(CxMap *map); diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/iterator.h --- a/ucx/cx/iterator.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/iterator.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file iterator.h - * \brief Interface for iterator implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file iterator.h + * @brief Interface for iterator implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_ITERATOR_H @@ -38,11 +38,18 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Common data for all iterators. + */ struct cx_iterator_base_s { /** * True iff the iterator points to valid data. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull bool (*valid)(const void *); /** @@ -50,13 +57,15 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current)(const void *); /** * Original implementation in case the function needs to be wrapped. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current_impl)(const void *); /** @@ -64,7 +73,7 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull void (*next)(void *); /** * Indicates whether this iterator may remove elements. @@ -86,6 +95,9 @@ * Internal iterator struct - use CxIterator. */ struct cx_iterator_s { + /** + * Inherited common data for all iterators. + */ CX_ITERATOR_BASE; /** @@ -141,7 +153,7 @@ /** * May contain the total number of elements, if known. - * Shall be set to \c SIZE_MAX when the total number is unknown during iteration. + * Shall be set to @c SIZE_MAX when the total number is unknown during iteration. */ size_t elem_count; }; @@ -154,7 +166,7 @@ * to be "position-aware", which means that they keep track of the current index within the collection. * * @note Objects that are pointed to by an iterator are always mutable through that iterator. However, - * any concurrent mutation of the collection other than by this iterator makes this iterator invalid + * any concurrent mutation of the collection other than by this iterator makes this iterator invalid, * and it must not be used anymore. */ typedef struct cx_iterator_s CxIterator; @@ -165,7 +177,8 @@ * This is especially false for past-the-end iterators. * * @param iter the iterator - * @return true iff the iterator points to valid data + * @retval true if the iterator points to valid data + * @retval false if the iterator already moved past the end */ #define cxIteratorValid(iter) (iter).base.valid(&(iter)) @@ -176,6 +189,7 @@ * * @param iter the iterator * @return a pointer to the current element + * @see cxIteratorValid() */ #define cxIteratorCurrent(iter) (iter).base.current(&iter) @@ -189,6 +203,8 @@ /** * Flags the current element for removal, if this iterator is mutating. * + * Does nothing for non-mutating iterators. + * * @param iter the iterator */ #define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating @@ -199,11 +215,13 @@ * This is useful for APIs that expect some iterator as an argument. * * @param iter the iterator + * @return (@c CxIterator*) a pointer to the iterator */ #define cxIteratorRef(iter) &((iter).base) /** * Loops over an iterator. + * * @param type the type of the elements * @param elem the name of the iteration variable * @param iter the iterator @@ -215,16 +233,21 @@ /** * Creates an iterator for the specified plain array. * - * The \p array can be \c NULL in which case the iterator will be immediately - * initialized such that #cxIteratorValid() returns \c false. + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. * + * This iterator yields the addresses of the array elements. + * If you want to iterator over an array of pointers, you can + * use cxIteratorPtr() to create an iterator which directly + * yields the stored pointers. * - * @param array a pointer to the array (can be \c NULL) + * @param array a pointer to the array (can be @c NULL) * @param elem_size the size of one array element * @param elem_count the number of elements in the array * @return an iterator for the specified array + * @see cxIteratorPtr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxIterator( const void *array, size_t elem_size, @@ -238,23 +261,23 @@ * elements through #cxIteratorFlagRemoval(). Every other change to the array * will bring this iterator to an undefined state. * - * When \p remove_keeps_order is set to \c false, removing an element will only + * When @p remove_keeps_order is set to @c false, removing an element will only * move the last element to the position of the removed element, instead of * moving all subsequent elements by one. Usually, when the order of elements is - * not important, this parameter should be set to \c false. + * not important, this parameter should be set to @c false. * - * The \p array can be \c NULL in which case the iterator will be immediately - * initialized such that #cxIteratorValid() returns \c false. + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. * * - * @param array a pointer to the array (can be \c NULL) + * @param array a pointer to the array (can be @c NULL) * @param elem_size the size of one array element * @param elem_count the number of elements in the array - * @param remove_keeps_order \c true if the order of elements must be preserved + * @param remove_keeps_order @c true if the order of elements must be preserved * when removing an element * @return an iterator for the specified array */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxMutIterator( void *array, size_t elem_size, @@ -262,4 +285,48 @@ bool remove_keeps_order ); +/** + * Creates an iterator for the specified plain pointer array. + * + * This iterator assumes that every element in the array is a pointer + * and yields exactly those pointers during iteration (while in contrast + * an iterator created with cxIterator() would return the addresses + * of those pointers within the array). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @return an iterator for the specified array + * @see cxIterator() + */ +cx_attr_nodiscard +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +); + +/** + * Creates a mutating iterator for the specified plain pointer array. + * + * This is the mutating variant of cxIteratorPtr(). See also + * cxMutIterator(). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @param remove_keeps_order @c true if the order of elements must be preserved + * when removing an element + * @return an iterator for the specified array + * @see cxMutIterator() + * @see cxIteratorPtr() + */ +cx_attr_nodiscard +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +); + +#ifdef __cplusplus +} // extern "C" +#endif + #endif // UCX_ITERATOR_H diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/json.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/json.h Mon Jan 06 22:22:55 2025 +0100 @@ -0,0 +1,1357 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file json.h + * @brief Interface for parsing data from JSON files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_JSON_H +#define UCX_JSON_H + +#include "common.h" +#include "allocator.h" +#include "string.h" +#include "buffer.h" +#include "array_list.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The type of the parsed token. + */ +enum cx_json_token_type { + /** + * No complete token parsed, yet. + */ + CX_JSON_NO_TOKEN, + /** + * The presumed token contains syntactical errors. + */ + CX_JSON_TOKEN_ERROR, + /** + * A "begin of array" '[' token. + */ + CX_JSON_TOKEN_BEGIN_ARRAY, + /** + * A "begin of object" '{' token. + */ + CX_JSON_TOKEN_BEGIN_OBJECT, + /** + * An "end of array" ']' token. + */ + CX_JSON_TOKEN_END_ARRAY, + /** + * An "end of object" '}' token. + */ + CX_JSON_TOKEN_END_OBJECT, + /** + * A colon ':' token separating names and values. + */ + CX_JSON_TOKEN_NAME_SEPARATOR, + /** + * A comma ',' token separating object entries or array elements. + */ + CX_JSON_TOKEN_VALUE_SEPARATOR, + /** + * A string token. + */ + CX_JSON_TOKEN_STRING, + /** + * A number token that can be represented as integer. + */ + CX_JSON_TOKEN_INTEGER, + /** + * A number token that cannot be represented as integer. + */ + CX_JSON_TOKEN_NUMBER, + /** + * A literal token. + */ + CX_JSON_TOKEN_LITERAL, + /** + * A white-space token. + */ + CX_JSON_TOKEN_SPACE +}; + +/** + * The type of some JSON value. + */ +enum cx_json_value_type { + /** + * Reserved. + */ + CX_JSON_NOTHING, // this allows us to always return non-NULL values + /** + * A JSON object. + */ + CX_JSON_OBJECT, + /** + * A JSON array. + */ + CX_JSON_ARRAY, + /** + * A string. + */ + CX_JSON_STRING, + /** + * A number that contains an integer. + */ + CX_JSON_INTEGER, + /** + * A number, not necessarily an integer. + */ + CX_JSON_NUMBER, + /** + * A literal (true, false, null). + */ + CX_JSON_LITERAL +}; + +/** + * JSON literal types. + */ +enum cx_json_literal { + /** + * The @c null literal. + */ + CX_JSON_NULL, + /** + * The @c true literal. + */ + CX_JSON_TRUE, + /** + * The @c false literal. + */ + CX_JSON_FALSE +}; + +/** + * Type alias for the token type enum. + */ +typedef enum cx_json_token_type CxJsonTokenType; +/** + * Type alias for the value type enum. + */ +typedef enum cx_json_value_type CxJsonValueType; + +/** + * Type alias for the JSON parser interface. + */ +typedef struct cx_json_s CxJson; + +/** + * Type alias for the token struct. + */ +typedef struct cx_json_token_s CxJsonToken; + +/** + * Type alias for the JSON value struct. + */ +typedef struct cx_json_value_s CxJsonValue; + +/** + * Type alias for the JSON array struct. + */ +typedef struct cx_json_array_s CxJsonArray; +/** + * Type alias for the JSON object struct. + */ +typedef struct cx_json_object_s CxJsonObject; +/** + * Type alias for a JSON string. + */ +typedef struct cx_mutstr_s CxJsonString; +/** + * Type alias for a number that can be represented as 64-bit signed integer. + */ +typedef int64_t CxJsonInteger; +/** + * Type alias for number that is not an integer. + */ +typedef double CxJsonNumber; +/** + * Type alias for a JSON literal. + */ +typedef enum cx_json_literal CxJsonLiteral; + +/** + * Type alias for a key/value pair in a JSON object. + */ +typedef struct cx_json_obj_value_s CxJsonObjValue; + +/** + * JSON array structure. + */ +struct cx_json_array_s { + /** + * The array data. + */ + CX_ARRAY_DECLARE(CxJsonValue*, array); +}; + +/** + * JSON object structure. + */ +struct cx_json_object_s { + /** + * The key/value entries. + */ + CX_ARRAY_DECLARE(CxJsonObjValue, values); + /** + * The original indices to reconstruct the order in which the members were added. + */ + size_t *indices; +}; + +/** + * Structure for a key/value entry in a JSON object. + */ +struct cx_json_obj_value_s { + /** + * The key (or name in JSON terminology) of the value. + */ + cxmutstr name; + /** + * The value. + */ + CxJsonValue *value; +}; + +/** + * Structure for a JSON value. + */ +struct cx_json_value_s { + /** + * The allocator with which the value was allocated. + * + * Required for recursively deallocating memory of objects and arrays. + */ + const CxAllocator *allocator; + /** + * The type of this value. + * + * Specifies how the @c value union shall be resolved. + */ + CxJsonValueType type; + /** + * The value data. + */ + union { + /** + * The array data if type is #CX_JSON_ARRAY. + */ + CxJsonArray array; + /** + * The object data if type is #CX_JSON_OBJECT. + */ + CxJsonObject object; + /** + * The string data if type is #CX_JSON_STRING. + */ + CxJsonString string; + /** + * The integer if type is #CX_JSON_INTEGER. + */ + CxJsonInteger integer; + /** + * The number if type is #CX_JSON_NUMBER. + */ + CxJsonNumber number; + /** + * The literal type if type is #CX_JSON_LITERAL. + */ + CxJsonLiteral literal; + } value; +}; + +/** + * Internally used structure for a parsed token. + * + * You should never need to use this in your code. + */ +struct cx_json_token_s { + /** + * The token type. + */ + CxJsonTokenType tokentype; + /** + * True, iff the @c content must be passed to cx_strfree(). + */ + bool allocated; + /** + * The token text, if any. + * + * This is not necessarily set when the token type already + * uniquely identifies the content. + */ + cxmutstr content; +}; + +/** + * The JSON parser interface. + */ +struct cx_json_s { + /** + * The allocator used for produced JSON values. + */ + const CxAllocator *allocator; + /** + * The input buffer. + */ + CxBuffer buffer; + + /** + * Used internally. + * + * Remembers the prefix of the last uncompleted token. + */ + CxJsonToken uncompleted; + + /** + * A pointer to an intermediate state of the currently parsed value. + * + * Never access this value manually. + */ + CxJsonValue *parsed; + + /** + * A pointer to an intermediate state of a currently parsed object member. + * + * Never access this value manually. + */ + CxJsonObjValue uncompleted_member; + + /** + * State stack. + */ + CX_ARRAY_DECLARE_SIZED(int, states, unsigned); + + /** + * Value buffer stack. + */ + CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned); + + /** + * Internally reserved memory for the state stack. + */ + int states_internal[8]; + + /** + * Internally reserved memory for the value buffer stack. + */ + CxJsonValue* vbuf_internal[8]; + + /** + * Used internally. + */ + bool tokenizer_escape; // TODO: check if it can be replaced with look-behind +}; + +/** + * Status codes for the json interface. + */ +enum cx_json_status { + /** + * Everything is fine. + */ + CX_JSON_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_JSON_NO_DATA, + /** + * The input ends unexpectedly. + * + * Refill the buffer with cxJsonFill() to complete the json data. + */ + CX_JSON_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_JSON_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_JSON_OK, + /** + * The input buffer has never been filled. + */ + CX_JSON_NULL_DATA, + /** + * Allocating memory for the internal buffer failed. + */ + CX_JSON_BUFFER_ALLOC_FAILED, + /** + * Allocating memory for a json value failed. + */ + CX_JSON_VALUE_ALLOC_FAILED, + /** + * A number value is incorrectly formatted. + */ + CX_JSON_FORMAT_ERROR_NUMBER, + /** + * The tokenizer found something unexpected. + */ + CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN +}; + +/** + * Typedef for the json status enum. + */ +typedef enum cx_json_status CxJsonStatus; + +/** + * The JSON writer settings. + */ +struct cx_json_writer_s { + /** + * Set true to enable pretty output. + */ + bool pretty; + /** + * Set false to output the members in the order in which they were added. + */ + bool sort_members; + /** + * The maximum number of fractional digits in a number value. + */ + uint8_t frac_max_digits; + /** + * Set true to use spaces instead of tab characters. + * Indentation is only used in pretty output. + */ + bool indent_space; + /** + * If @c indent_space is true, this is the number of spaces per tab. + * Indentation is only used in pretty output. + */ + uint8_t indent; +}; + +/** + * Typedef for the json writer. + */ +typedef struct cx_json_writer_s CxJsonWriter; + +/** + * Creates a default writer configuration for compact output. + * + * @return new JSON writer settings + */ +cx_attr_nodiscard +CxJsonWriter cxJsonWriterCompact(void); + +/** + * Creates a default writer configuration for pretty output. + * + * @param use_spaces false if you want tabs, true if you want four spaces instead + * @return new JSON writer settings + */ +cx_attr_nodiscard +CxJsonWriter cxJsonWriterPretty(bool use_spaces); + +/** + * Writes a JSON value to a buffer or stream. + * + * This function blocks until all data is written or an error when trying + * to write data occurs. + * The write operation is not atomic in the sense that it might happen + * that the data is only partially written when an error occurs with no + * way to indicate how much data was written. + * To avoid this problem, you can use a CxBuffer as @p target which is + * unlikely to fail a write operation and either use the buffer's flush + * feature to relay the data or use the data in the buffer manually to + * write it to the actual target. + * + * @param target the buffer or stream where to write to + * @param value the value that shall be written + * @param wfunc the write function to use + * @param settings formatting settings (or @c NULL to use a compact default) + * @retval zero success + * @retval non-zero when no or not all data could be written + */ +cx_attr_nonnull_arg(1, 2, 3) +int cxJsonWrite( + void* target, + const CxJsonValue* value, + cx_write_func wfunc, + const CxJsonWriter* settings +); + +/** + * Initializes the json interface. + * + * @param json the json interface + * @param allocator the allocator that shall be used for the produced values + * @see cxJsonDestroy() + */ +cx_attr_nonnull_arg(1) +void cxJsonInit(CxJson *json, const CxAllocator *allocator); + +/** + * Destroys the json interface. + * + * @param json the json interface + * @see cxJsonInit() + */ +cx_attr_nonnull +void cxJsonDestroy(CxJson *json); + +/** + * Destroys and re-initializes the json interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param json the json interface + */ +cx_attr_nonnull +static inline void cxJsonReset(CxJson *json) { + const CxAllocator *allocator = json->allocator; + cxJsonDestroy(json); + cxJsonInit(json, allocator); +} + +/** + * Fills the input buffer. + * + * @remark The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param buf the source buffer + * @param len the length of the source buffer + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonFilln(CxJson *json, const char *buf, size_t len); + +#ifdef __cplusplus +} // extern "C" + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxJsonFill( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer. + * + * The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param str the source string + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFilln() + */ +#define cxJsonFill(json, str) _Generic((str), \ + cxstring: cx_json_fill_cxstr, \ + cxmutstr: cx_json_fill_mutstr, \ + char*: cx_json_fill_str, \ + const char*: cx_json_fill_str) \ + (json, str) + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_cxstr( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_mutstr( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_json_fill_str( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} +#endif + +/** + * Creates a new (empty) JSON object. + * + * @param allocator the allocator to use + * @return the new JSON object or @c NULL if allocation fails + * @see cxJsonObjPutObj() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); + +/** + * Creates a new (empty) JSON array. + * + * @param allocator the allocator to use + * @return the new JSON array or @c NULL if allocation fails + * @see cxJsonObjPutArr() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); + +/** + * Creates a new JSON number value. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutNumber() + * @see cxJsonArrAddNumbers() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); + +/** + * Creates a new JSON number value based on an integer. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutInteger() + * @see cxJsonArrAddIntegers() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateString() + * @see cxJsonObjPutString() + * @see cxJsonArrAddStrings() + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(2) +cx_attr_cstr_arg(2) +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateCxString() + * @see cxJsonObjPutCxString() + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); + +/** + * Creates a new JSON literal. + * + * @param allocator the allocator to use + * @param lit the type of literal + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutLiteral() + * @see cxJsonArrAddLiterals() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); + +/** + * Adds number values to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); + +/** + * Adds number values, of which all are integers, to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); + +/** + * Adds literals to a JSON array. + * + * @param arr the JSON array + * @param lit the array of literal types + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); + +/** + * Add arbitrary values to a JSON array. + * + * @attention In contrast to all other add functions, this function adds the values + * directly to the array instead of copying them. + * + * @param arr the JSON array + * @param val the values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); + +/** + * Adds or replaces a value within a JSON object. + * + * The value will be directly added and not copied. + * + * @note If a value with the specified @p name already exists, + * it will be (recursively) freed with its own allocator. + * + * @param obj the JSON object + * @param name the name of the value + * @param child the value + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); + +/** + * Creates a new JSON object and adds it to an existing object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateObj() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON array and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateArr() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON number and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateNumber() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); + +/** + * Creates a new JSON number, based on an integer, and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateInteger() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateString() + */ +cx_attr_nonnull +cx_attr_cstr_arg(3) +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateCxString() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); + +/** + * Creates a new JSON literal and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param lit the type of literal + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateLiteral() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); + +/** + * Recursively deallocates the memory of a JSON value. + * + * @remark The type of each deallocated value will be changed + * to #CX_JSON_NOTHING and values of such type will be skipped + * by the de-allocation. That means, this function protects + * you from double-frees when you are accidentally freeing + * a nested value and then the parent value (or vice versa). + * + * @param value the value + */ +void cxJsonValueFree(CxJsonValue *value); + +/** + * Tries to obtain the next JSON value. + * + * Before this function can be called, the input buffer needs + * to be filled with cxJsonFill(). + * + * When this function returns #CX_JSON_INCOMPLETE_DATA, you can + * add the missing data with another invocation of cxJsonFill() + * and then repeat the call to cxJsonNext(). + * + * @param json the json interface + * @param value a pointer where the next value shall be stored + * @retval CX_JSON_NO_ERROR successfully retrieve the @p value + * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from + * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read + * and more data needs to be filled + * @retval CX_JSON_NULL_DATA the buffer was never initialized + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +cx_attr_nonnull +cx_attr_access_w(2) +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); + +/** + * Checks if the specified value is a JSON object. + * + * @param value a pointer to the value + * @retval true the value is a JSON object + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsObject(const CxJsonValue *value) { + return value->type == CX_JSON_OBJECT; +} + +/** + * Checks if the specified value is a JSON array. + * + * @param value a pointer to the value + * @retval true the value is a JSON array + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsArray(const CxJsonValue *value) { + return value->type == CX_JSON_ARRAY; +} + +/** + * Checks if the specified value is a string. + * + * @param value a pointer to the value + * @retval true the value is a string + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsString(const CxJsonValue *value) { + return value->type == CX_JSON_STRING; +} + +/** + * Checks if the specified value is a JSON number. + * + * This function will return true for both floating point and + * integer numbers. + * + * @param value a pointer to the value + * @retval true the value is a JSON number + * @retval false otherwise + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline bool cxJsonIsNumber(const CxJsonValue *value) { + return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is an integer number. + * + * @param value a pointer to the value + * @retval true the value is an integer number + * @retval false otherwise + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline bool cxJsonIsInteger(const CxJsonValue *value) { + return value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is a JSON literal. + * + * JSON literals are @c true, @c false, and @c null. + * + * @param value a pointer to the value + * @retval true the value is a JSON literal + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + * @see cxJsonIsNull() + */ +cx_attr_nonnull +static inline bool cxJsonIsLiteral(const CxJsonValue *value) { + return value->type == CX_JSON_LITERAL; +} + +/** + * Checks if the specified value is a Boolean literal. + * + * @param value a pointer to the value + * @retval true the value is either @c true or @c false + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsBool(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; +} + +/** + * Checks if the specified value is @c true. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsFalse(v). + * + * @param value a pointer to the value + * @retval true the value is @c true + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsTrue(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; +} + +/** + * Checks if the specified value is @c false. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsTrue(v). + * + * @param value a pointer to the value + * @retval true the value is @c false + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsTrue() + */ +cx_attr_nonnull +static inline bool cxJsonIsFalse(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; +} + +/** + * Checks if the specified value is @c null. + * + * @param value a pointer to the value + * @retval true the value is @c null + * @retval false otherwise + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonIsNull(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; +} + +/** + * Obtains a C string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as C string + * @see cxJsonIsString() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline char *cxJsonAsString(const CxJsonValue *value) { + return value->value.string.ptr; +} + +/** + * Obtains a UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { + return cx_strcast(value->value.string); +} + +/** + * Obtains a mutable UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as mutable UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { + return value->value.string; +} + +/** + * Obtains a double-precision floating point value from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline double cxJsonAsDouble(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return (double) value->value.integer; + } else { + return value->value.number; + } +} + +/** + * Obtains a 64-bit signed integer from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * If it is a JSON number, but not an integer, the value will be + * converted to an integer, possibly losing precision. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return value->value.integer; + } else { + return (int64_t) value->value.number; + } +} + +/** + * Obtains a Boolean value from the given JSON value. + * + * If the @p value is not a JSON literal, the behavior is undefined. + * The @c null literal is interpreted as @c false. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonAsBool(const CxJsonValue *value) { + return value->value.literal == CX_JSON_TRUE; +} + +/** + * Returns the size of a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return the size of the array + * @see cxJsonIsArray() + */ +cx_attr_nonnull +static inline size_t cxJsonArrSize(const CxJsonValue *value) { + return value->value.array.array_size; +} + +/** + * Returns an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function guarantees to return a value. If the index is + * out of bounds, the returned value will be of type + * #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON value + * @param index the index in the array + * @return the value at the specified index + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); + +/** + * Returns an iterator over the JSON array elements. + * + * The iterator yields values of type @c CxJsonValue* . + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the array elements + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxJsonArrIter(const CxJsonValue *value); + +/** + * Returns an iterator over the JSON object members. + * + * The iterator yields values of type @c CxJsonObjValue* which + * contain the name and value of the member. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the object members + * @see cxJsonIsObject() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxJsonObjIter(const CxJsonValue *value); + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); + +#ifdef __cplusplus +} // extern "C" + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { + return cx_json_obj_get_cxstr(value, name); +} + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} + +extern "C" { +#else +/** + * Returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function guarantees to return a JSON value. If the + * object does not contain @p name, the returned JSON value + * will be of type #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key + * @see cxJsonIsObject() + */ +#define cxJsonObjGet(value, name) _Generic((name), \ + cxstring: cx_json_obj_get_cxstr, \ + cxmutstr: cx_json_obj_get_mutstr, \ + char*: cx_json_obj_get_str, \ + const char*: cx_json_obj_get_str) \ + (value, name) + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_cstr_arg(2) +static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_JSON_H */ + diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/linked_list.h --- a/ucx/cx/linked_list.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/linked_list.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,12 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file linked_list.h - * \brief Linked list implementation. - * \details Also provides several low-level functions for custom linked list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file linked_list.h + * @brief Linked list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_LINKED_LIST_H @@ -46,24 +45,28 @@ /** * The maximum item size that uses SBO swap instead of relinking. + * */ -extern unsigned cx_linked_list_swap_sbo_size; +extern const unsigned cx_linked_list_swap_sbo_size; /** - * Allocates a linked list for storing elements with \p elem_size bytes each. + * Allocates a linked list for storing elements with @p elem_size bytes each. * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list nodes - * (if \c NULL the cxDefaultAllocator will be used) + * (if @c NULL, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL, and the list is not storing pointers, sort and find + * (if @c NULL, and the list is not storing pointers, sort and find * functions will not work) * @param elem_size the size of each element in bytes * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) CxList *cxLinkedListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -71,18 +74,18 @@ ); /** - * Allocates a linked list for storing elements with \p elem_size bytes each. + * Allocates a linked list for storing elements with @p elem_size bytes each. * * The list will use cxDefaultAllocator and no comparator function. If you want * to call functions that need a comparator, you must either set one immediately * after list creation or use cxLinkedListCreate(). * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(). * - * @param elem_size the size of each element in bytes - * @return the created list + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list */ #define cxLinkedListCreateSimple(elem_size) \ cxLinkedListCreate(NULL, NULL, elem_size) @@ -91,11 +94,11 @@ * Finds the node at a certain index. * * This function can be used to start at an arbitrary position within the list. - * If the search index is large than the start index, \p loc_advance must denote - * the location of some sort of \c next pointer (i.e. a pointer to the next node). + * If the search index is large than the start index, @p loc_advance must denote + * the location of some sort of @c next pointer (i.e. a pointer to the next node). * But it is also possible that the search index is smaller than the start index * (e.g. in cases where traversing a list backwards is faster) in which case - * \p loc_advance must denote the location of some sort of \c prev pointer + * @p loc_advance must denote the location of some sort of @c prev pointer * (i.e. a pointer to the previous node). * * @param start a pointer to the start node @@ -104,7 +107,8 @@ * @param index the search index * @return the node found at the specified index */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard void *cx_linked_list_at( const void *start, size_t start_index, @@ -117,12 +121,12 @@ * * @param start a pointer to the start node * @param loc_advance the location of the pointer to advance - * @param loc_data the location of the \c data pointer within your node struct - * @param cmp_func a compare function to compare \p elem against the node data + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func a compare function to compare @p elem against the node data * @param elem a pointer to the element to find * @return the index of the element or a negative value if it could not be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find( const void *start, ptrdiff_t loc_advance, @@ -134,16 +138,16 @@ /** * Finds the node containing an element within a linked list. * - * @param result a pointer to the memory where the node pointer (or \c NULL if the element + * @param result a pointer to the memory where the node pointer (or @c NULL if the element * could not be found) shall be stored to * @param start a pointer to the start node * @param loc_advance the location of the pointer to advance - * @param loc_data the location of the \c data pointer within your node struct - * @param cmp_func a compare function to compare \p elem against the node data + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func a compare function to compare @p elem against the node data * @param elem a pointer to the element to find * @return the index of the element or a negative value if it could not be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find_node( void **result, const void *start, @@ -156,15 +160,16 @@ /** * Finds the first node in a linked list. * - * The function starts with the pointer denoted by \p node and traverses the list + * The function starts with the pointer denoted by @p node and traverses the list * along a prev pointer whose location within the node struct is - * denoted by \p loc_prev. + * denoted by @p loc_prev. * * @param node a pointer to a node in the list - * @param loc_prev the location of the \c prev pointer + * @param loc_prev the location of the @c prev pointer * @return a pointer to the first node */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_first( const void *node, ptrdiff_t loc_prev @@ -173,15 +178,16 @@ /** * Finds the last node in a linked list. * - * The function starts with the pointer denoted by \p node and traverses the list + * The function starts with the pointer denoted by @p node and traverses the list * along a next pointer whose location within the node struct is - * denoted by \p loc_next. + * denoted by @p loc_next. * * @param node a pointer to a node in the list - * @param loc_next the location of the \c next pointer + * @param loc_next the location of the @c next pointer * @return a pointer to the last node */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_last( const void *node, ptrdiff_t loc_next @@ -190,14 +196,14 @@ /** * Finds the predecessor of a node in case it is not linked. * - * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined. + * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined. * * @param begin the node where to start the search - * @param loc_next the location of the \c next pointer + * @param loc_next the location of the @c next pointer * @param node the successor of the node to find - * @return the node or \c NULL if \p node has no predecessor + * @return the node or @c NULL if @p node has no predecessor */ -__attribute__((__nonnull__)) +cx_attr_nonnull void *cx_linked_list_prev( const void *begin, ptrdiff_t loc_next, @@ -208,15 +214,15 @@ * Adds a new node to a linked list. * The node must not be part of any list already. * - * \remark One of the pointers \p begin or \p end may be \c NULL, but not both. + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be appended */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_add( void **begin, void **end, @@ -229,15 +235,15 @@ * Prepends a new node to a linked list. * The node must not be part of any list already. * - * \remark One of the pointers \p begin or \p end may be \c NULL, but not both. + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be prepended */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_prepend( void **begin, void **end, @@ -249,12 +255,12 @@ /** * Links two nodes. * - * @param left the new predecessor of \p right - * @param right the new successor of \p left - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param left the new predecessor of @p right + * @param right the new successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_link( void *left, void *right, @@ -267,12 +273,12 @@ * * If right is not the successor of left, the behavior is undefined. * - * @param left the predecessor of \p right - * @param right the successor of \p left - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param left the predecessor of @p right + * @param right the successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_unlink( void *left, void *right, @@ -284,17 +290,17 @@ * Inserts a new node after a given node of a linked list. * The new node must not be part of any list already. * - * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or - * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list. + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param node the node after which to insert (\c NULL if you want to prepend the node to the list) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL if you want to prepend the node to the list) * @param new_node a pointer to the node that shall be inserted */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert( void **begin, void **end, @@ -309,22 +315,22 @@ * The chain must not be part of any list already. * * If you do not explicitly specify the end of the chain, it will be determined by traversing - * the \c next pointer. + * the @c next pointer. * - * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or - * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need - * to provide a valid \p loc_prev location. + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need + * to provide a valid @p loc_prev location. * Then the chain will be prepended to the list. * - * @param begin a pointer to the begin node pointer (if your list has one) + * @param begin a pointer to the beginning node pointer (if your list has one) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param node the node after which to insert (\c NULL to prepend the chain to the list) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL to prepend the chain to the list) * @param insert_begin a pointer to the first node of the chain that shall be inserted * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert_chain( void **begin, void **end, @@ -339,17 +345,17 @@ * Inserts a node into a sorted linked list. * The new node must not be part of any list already. * - * If the list starting with the node pointed to by \p begin is not sorted + * If the list starting with the node pointed to by @p begin is not sorted * already, the behavior is undefined. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be inserted * @param cmp_func a compare function that will receive the node pointers */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted( void **begin, void **end, @@ -363,22 +369,22 @@ * Inserts a chain of nodes into a sorted linked list. * The chain must not be part of any list already. * - * If either the list starting with the node pointed to by \p begin or the list - * starting with \p insert_begin is not sorted, the behavior is undefined. + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. * - * \attention In contrast to cx_linked_list_insert_chain(), the source chain + * @attention In contrast to cx_linked_list_insert_chain(), the source chain * will be broken and inserted into the target list so that the resulting list - * will be sorted according to \p cmp_func. That means, each node in the source + * will be sorted according to @p cmp_func. That means, each node in the source * chain may be re-linked with nodes from the target list. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (if your list has one) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param insert_begin a pointer to the first node of the chain that shall be inserted * @param cmp_func a compare function that will receive the node pointers */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -389,39 +395,72 @@ ); /** - * Removes a node from the linked list. + * Removes a chain of nodes from the linked list. * - * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end) + * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): - * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) - * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance) + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) + * + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used + * to traverse to a former adjacent node in the list, or within the chain. * - * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used + * @param begin a pointer to the beginning node pointer (optional) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the start node of the chain + * @param num the number of nodes to remove + * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) + */ +cx_attr_nonnull_arg(5) +size_t cx_linked_list_remove_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + size_t num +); + +/** + * Removes a node from the linked list. + * + * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * addresses are provided, the pointers are adjusted accordingly. + * + * The following combinations of arguments are valid (more arguments are optional): + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) + * + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used * to traverse to a former adjacent node in the list. * - * @param begin a pointer to the begin node pointer (optional) + * @param begin a pointer to the beginning node pointer (optional) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) * @param node the node to remove */ -__attribute__((__nonnull__(5))) -void cx_linked_list_remove( +cx_attr_nonnull_arg(5) +static inline void cx_linked_list_remove( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node -); - +) { + cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); +} /** - * Determines the size of a linked list starting with \p node. + * Determines the size of a linked list starting with @p node. + * * @param node the first node - * @param loc_next the location of the \c next pointer within the node struct - * @return the size of the list or zero if \p node is \c NULL + * @param loc_next the location of the @c next pointer within the node struct + * @return the size of the list or zero if @p node is @c NULL */ size_t cx_linked_list_size( const void *node, @@ -432,25 +471,25 @@ * Sorts a linked list based on a comparison function. * * This function can work with linked lists of the following structure: - * \code + * @code * typedef struct node node; * struct node { * node* prev; * node* next; * my_payload data; * } - * \endcode + * @endcode * * @note This is a recursive function with at most logarithmic recursion depth. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present) - * @param loc_next the location of a \c next pointer within your node struct (required) - * @param loc_data the location of the \c data pointer within your node struct + * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param loc_data the location of the @c data pointer within your node struct * @param cmp_func the compare function defining the sort order */ -__attribute__((__nonnull__(1, 6))) +cx_attr_nonnull_arg(1, 6) void cx_linked_list_sort( void **begin, void **end, @@ -464,17 +503,17 @@ /** * Compares two lists element wise. * - * \note Both list must have the same structure. + * @attention Both list must have the same structure. * - * @param begin_left the begin of the left list (\c NULL denotes an empty list) - * @param begin_right the begin of the right list (\c NULL denotes an empty list) + * @param begin_left the beginning of the left list (@c NULL denotes an empty list) + * @param begin_right the beginning of the right list (@c NULL denotes an empty list) * @param loc_advance the location of the pointer to advance - * @param loc_data the location of the \c data pointer within your node struct + * @param loc_data the location of the @c data pointer within your node struct * @param cmp_func the function to compare the elements - * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the + * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the * right list, positive if the left list is larger than the right list, zero if both lists are equal. */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) int cx_linked_list_compare( const void *begin_left, const void *begin_right, @@ -486,12 +525,12 @@ /** * Reverses the order of the nodes in a linked list. * - * @param begin a pointer to the begin node pointer (required) + * @param begin a pointer to the beginning node pointer (required) * @param end a pointer to the end node pointer (optional) - * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) - * @param loc_next the location of a \c next pointer within your node struct (required) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) void cx_linked_list_reverse( void **begin, void **end, diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/list.h --- a/ucx/cx/list.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/list.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file list.h - * \brief Interface for list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file list.h + * @brief Interface for list implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_LIST_H @@ -52,6 +52,9 @@ * Structure for holding the base data of a list. */ struct cx_list_s { + /** + * Common members for collections. + */ CX_COLLECTION_BASE; /** * The list class definition. @@ -71,9 +74,9 @@ * Destructor function. * * Implementations SHALL invoke the content destructor functions if provided - * and SHALL deallocate the list memory. + * and SHALL deallocate the entire list memory. */ - void (*destructor)(struct cx_list_s *list); + void (*deallocate)(struct cx_list_s *list); /** * Member function for inserting a single element. @@ -88,6 +91,7 @@ /** * Member function for inserting multiple elements. * Implementors SHOULD see to performant implementations for corner cases. + * * @see cx_list_default_insert_array() */ size_t (*insert_array)( @@ -118,11 +122,20 @@ ); /** - * Member function for removing an element. + * Member function for removing elements. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return the actual number of elements removed, which + * might be lower than @p num when going out of bounds. */ - int (*remove)( + size_t (*remove)( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ); /** @@ -132,6 +145,7 @@ /** * Member function for swapping two elements. + * * @see cx_list_default_swap() */ int (*swap)( @@ -158,7 +172,8 @@ ); /** - * Member function for sorting the list in-place. + * Member function for sorting the list. + * * @see cx_list_default_sort() */ void (*sort)(struct cx_list_s *list); @@ -166,8 +181,9 @@ /** * Optional member function for comparing this list * to another list of the same type. - * If set to \c NULL, comparison won't be optimized. + * If set to @c NULL, comparison won't be optimized. */ + cx_attr_nonnull int (*compare)( const struct cx_list_s *list, const struct cx_list_s *other @@ -202,7 +218,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_array( struct cx_list_s *list, size_t index, @@ -216,7 +232,7 @@ * This function uses the array insert function to insert consecutive groups * of sorted data. * - * The source data \em must already be sorted wrt. the list's compare function. + * The source data @em must already be sorted wrt. the list's compare function. * * Use this in your own list class if you do not want to implement an optimized * version for your list. @@ -226,7 +242,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_sorted( struct cx_list_s *list, const void *sorted_data, @@ -244,7 +260,7 @@ * * @param list the list that shall be sorted */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_list_default_sort(struct cx_list_s *list); /** @@ -256,10 +272,11 @@ * @param list the list in which to swap * @param i index of one element * @param j index of the other element - * @return zero on success, non-zero when indices are out of bounds or memory + * @retval zero success + * @retval non-zero when indices are out of bounds or memory * allocation for the temporary buffer fails */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** @@ -276,7 +293,7 @@ * @param list the list * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStoreObjects(CxList *list); /** @@ -291,7 +308,7 @@ * @param list the list * @see cxListStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStorePointers(CxList *list); /** @@ -301,7 +318,7 @@ * @return true, if this list is storing pointers * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxListIsStoringPointers(const CxList *list) { return list->collection.store_pointer; } @@ -312,7 +329,7 @@ * @param list the list * @return the number of currently stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListSize(const CxList *list) { return list->collection.size; } @@ -322,10 +339,11 @@ * * @param list the list * @param elem a pointer to the element to add - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListAddArray() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListAdd( CxList *list, const void *elem @@ -339,9 +357,9 @@ * This method is more efficient than invoking cxListAdd() multiple times. * * If there is not enough memory to add all elements, the returned value is - * less than \p n. + * less than @p n. * - * If this list is storing pointers instead of objects \p array is expected to + * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * * @param list the list @@ -349,7 +367,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListAddArray( CxList *list, const void *array, @@ -361,17 +379,17 @@ /** * Inserts an item at the specified index. * - * If \p index equals the list \c size, this is effectively cxListAdd(). + * If @p index equals the list @c size, this is effectively cxListAdd(). * * @param list the list * @param index the index the element shall have * @param elem a pointer to the element to add - * @return zero on success, non-zero on memory allocation failure - * or when the index is out of bounds + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds * @see cxListInsertAfter() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsert( CxList *list, size_t index, @@ -385,9 +403,10 @@ * * @param list the list * @param elem a pointer to the element to add - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertSorted( CxList *list, const void *elem @@ -398,15 +417,15 @@ /** * Inserts multiple items to the list at the specified index. - * If \p index equals the list size, this is effectively cxListAddArray(). + * If @p index equals the list size, this is effectively cxListAddArray(). * * This method is usually more efficient than invoking cxListInsert() * multiple times. * * If there is not enough memory to add all elements, the returned value is - * less than \p n. + * less than @p n. * - * If this list is storing pointers instead of objects \p array is expected to + * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * * @param list the list @@ -415,7 +434,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertArray( CxList *list, size_t index, @@ -432,9 +451,9 @@ * because consecutive chunks of sorted data are inserted in one pass. * * If there is not enough memory to add all elements, the returned value is - * less than \p n. + * less than @p n. * - * If this list is storing pointers instead of objects \p array is expected to + * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * * @param list the list @@ -442,7 +461,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertSortedArray( CxList *list, const void *array, @@ -457,16 +476,17 @@ * The used iterator remains operational, but all other active iterators should * be considered invalidated. * - * If \p iter is not a list iterator, the behavior is undefined. - * If \p iter is a past-the-end iterator, the new element gets appended to the list. + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. * * @param iter an iterator * @param elem the element to insert - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListInsert() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertAfter( CxIterator *iter, const void *elem @@ -480,16 +500,17 @@ * The used iterator remains operational, but all other active iterators should * be considered invalidated. * - * If \p iter is not a list iterator, the behavior is undefined. - * If \p iter is a past-the-end iterator, the new element gets appended to the list. + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. * * @param iter an iterator * @param elem the element to insert - * @return zero on success, non-zero on memory allocation failure + * @retval zero success + * @retval non-zero memory allocation failure * @see cxListInsert() * @see cxListInsertAfter() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertBefore( CxIterator *iter, const void *elem @@ -505,25 +526,95 @@ * * @param list the list * @param index the index of the element - * @return zero on success, non-zero if the index is out of bounds + * @retval zero success + * @retval non-zero index out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListRemove( CxList *list, size_t index ) { - return list->cl->remove(list, index); + return list->cl->remove(list, index, 1, NULL) == 0; +} + +/** + * Removes and returns the element at the specified index. + * + * No destructor is called and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * + * @param list the list + * @param index the index of the element + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero index out of bounds + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxListRemoveAndGet( + CxList *list, + size_t index, + void *targetbuf +) { + return list->cl->remove(list, index, 1, targetbuf) == 0; +} + +/** + * Removes multiple element starting at the specified index. + * + * If an element destructor function is specified, it is called for each + * element. It is guaranteed that the destructor is called before removing + * the element, however, due to possible optimizations it is neither guaranteed + * that the destructors are invoked for all elements before starting to remove + * them, nor that the element is removed immediately after the destructor call + * before proceeding to the next element. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @return the actual number of removed elements + */ +cx_attr_nonnull +static inline size_t cxListRemoveArray( + CxList *list, + size_t index, + size_t num +) { + return list->cl->remove(list, index, num, NULL); +} + +/** + * Removes and returns multiple element starting at the specified index. + * + * No destructor is called and instead the elements are copied to the + * @p targetbuf which MUST be large enough to hold all removed elements. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @param targetbuf a buffer where to copy the elements + * @return the actual number of removed elements + */ +cx_attr_nonnull +cx_attr_access_w(4) +static inline size_t cxListRemoveArrayAndGet( + CxList *list, + size_t index, + size_t num, + void *targetbuf +) { + return list->cl->remove(list, index, num, targetbuf); } /** * Removes all elements from this list. * - * If an element destructor function is specified, it is called for each + * If element destructor functions are specified, they are called for each * element before removing them. * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListClear(CxList *list) { list->cl->clear(list); } @@ -537,9 +628,11 @@ * @param list the list * @param i the index of the first element * @param j the index of the second element - * @return zero on success, non-zero if one of the indices is out of bounds + * @retval zero success + * @retval non-zero one of the indices is out of bounds + * or the swap needed extra memory but allocation failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListSwap( CxList *list, size_t i, @@ -553,11 +646,11 @@ * * @param list the list * @param index the index of the element - * @return a pointer to the element or \c NULL if the index is out of bounds + * @return a pointer to the element or @c NULL if the index is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void *cxListAt( - CxList *list, + const CxList *list, size_t index ) { return list->cl->at(list, index); @@ -574,7 +667,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIteratorAt( const CxList *list, size_t index @@ -593,7 +687,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIteratorAt( const CxList *list, size_t index @@ -612,7 +707,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxListMutIteratorAt( CxList *list, size_t index @@ -630,7 +726,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index @@ -646,7 +743,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIterator(const CxList *list) { return list->cl->iterator(list, 0, false); } @@ -661,7 +759,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutIterator(CxList *list) { return cxListMutIteratorAt(list, 0); } @@ -677,7 +776,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIterator(const CxList *list) { return list->cl->iterator(list, list->collection.size - 1, true); } @@ -692,13 +792,14 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutBackwardsIterator(CxList *list) { return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); } /** - * Returns the index of the first element that equals \p elem. + * Returns the index of the first element that equals @p elem. * * Determining equality is performed by the list's comparator function. * @@ -707,7 +808,8 @@ * @return the index of the element or a negative * value when the element is not found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline ssize_t cxListFind( const CxList *list, const void *elem @@ -716,7 +818,7 @@ } /** - * Removes and returns the index of the first element that equals \p elem. + * Removes and returns the index of the first element that equals @p elem. * * Determining equality is performed by the list's comparator function. * @@ -725,7 +827,7 @@ * @return the index of the now removed element or a negative * value when the element is not found or could not be removed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline ssize_t cxListFindRemove( CxList *list, const void *elem @@ -734,13 +836,13 @@ } /** - * Sorts the list in-place. + * Sorts the list. * - * \remark The underlying sort algorithm is implementation defined. + * @remark The underlying sort algorithm is implementation defined. * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListSort(CxList *list) { list->cl->sort(list); } @@ -750,7 +852,7 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListReverse(CxList *list) { list->cl->reverse(list); } @@ -763,10 +865,14 @@ * * @param list the list * @param other the list to compare to - * @return zero, if both lists are equal element wise, - * negative if the first list is smaller, positive if the first list is larger + * @retval zero both lists are equal element wise + * @retval negative the first list is smaller + * or the first non-equal element in the first list is smaller + * @retval positive the first list is larger + * or the first non-equal element in the first list is larger */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cxListCompare( const CxList *list, const CxList *other @@ -775,22 +881,21 @@ /** * Deallocates the memory of the specified list structure. * - * Also calls content a destructor function, depending on the configuration - * in CxList.content_destructor_type. - * - * This function itself is a destructor function for the CxList. + * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be destroyed + * @param list the list which shall be freed */ -__attribute__((__nonnull__)) -void cxListDestroy(CxList *list); +void cxListFree(CxList *list); /** * A shared instance of an empty list. * - * Writing to that list is undefined. + * Writing to that list is not allowed. + * + * You can use this is a placeholder for initializing CxList pointers + * for which you do not want to reserve memory right from the beginning. */ -extern CxList * const cxEmptyList; +extern CxList *const cxEmptyList; #ifdef __cplusplus diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/map.h --- a/ucx/cx/map.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/map.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file map.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file map.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_MAP_H @@ -89,19 +89,16 @@ /** * Deallocates the entire memory. */ - __attribute__((__nonnull__)) - void (*destructor)(struct cx_map_s *map); + void (*deallocate)(struct cx_map_s *map); /** * Removes all elements. */ - __attribute__((__nonnull__)) void (*clear)(struct cx_map_s *map); /** * Add or overwrite an element. */ - __attribute__((__nonnull__)) int (*put)( CxMap *map, CxHashKey key, @@ -111,7 +108,6 @@ /** * Returns an element. */ - __attribute__((__nonnull__, __warn_unused_result__)) void *(*get)( const CxMap *map, CxHashKey key @@ -119,18 +115,23 @@ /** * Removes an element. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return zero when the @p key was found and + * non-zero, otherwise. */ - __attribute__((__nonnull__)) - void *(*remove)( + int (*remove)( CxMap *map, CxHashKey key, - bool destroy + void *targetbuf ); /** * Creates an iterator for this map. */ - __attribute__((__nonnull__, __warn_unused_result__)) CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); }; @@ -151,7 +152,10 @@ /** * A shared instance of an empty map. * - * Writing to that map is undefined. + * Writing to that map is not allowed. + * + * You can use this is a placeholder for initializing CxMap pointers + * for which you do not want to reserve memory right from the beginning. */ extern CxMap *const cxEmptyMap; @@ -164,7 +168,7 @@ * @param map the map * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStoreObjects(CxMap *map) { map->collection.store_pointer = false; } @@ -181,7 +185,7 @@ * @param map the map * @see cxMapStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStorePointers(CxMap *map) { map->collection.store_pointer = true; map->collection.elem_size = sizeof(void *); @@ -194,7 +198,7 @@ * @return true, if this map is storing pointers * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxMapIsStoringPointers(const CxMap *map) { return map->collection.store_pointer; } @@ -202,20 +206,21 @@ /** * Deallocates the memory of the specified map. * - * @param map the map to be destroyed + * Also calls the content destructor functions for each element, if specified. + * + * @param map the map to be freed */ -__attribute__((__nonnull__)) -static inline void cxMapDestroy(CxMap *map) { - map->cl->destructor(map); -} +void cxMapFree(CxMap *map); /** * Clears a map by removing all elements. * + * Also calls the content destructor functions for each element, if specified. + * * @param map the map to be cleared */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapClear(CxMap *map) { map->cl->clear(map); } @@ -226,24 +231,22 @@ * @param map the map * @return the number of stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxMapSize(const CxMap *map) { return map->collection.size; } - -// TODO: set-like map operations (union, intersect, difference) - /** * Creates a value iterator for a map. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorValues(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } @@ -253,13 +256,14 @@ * * The elements of the iterator are keys of type CxHashKey. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorKeys(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } @@ -269,7 +273,7 @@ * * The elements of the iterator are key/value pairs of type CxMapEntry. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for @@ -277,7 +281,8 @@ * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIterator(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -286,13 +291,14 @@ /** * Creates a mutating iterator over the values of a map. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorValues(CxMap *map); /** @@ -300,13 +306,14 @@ * * The elements of the iterator are keys of type CxHashKey. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorKeys(CxMap *map); /** @@ -314,7 +321,7 @@ * * The elements of the iterator are key/value pairs of type CxMapEntry. * - * \note An iterator iterates over all elements successively. Therefore the order + * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * * @param map the map to create the iterator for @@ -322,21 +329,13 @@ * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIterator(CxMap *map); #ifdef __cplusplus } // end the extern "C" block here, because we want to start overloading - -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, CxHashKey const &key, @@ -345,16 +344,7 @@ return map->cl->put(map, key, value); } - -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxstring const &key, @@ -363,15 +353,7 @@ return map->cl->put(map, cx_hash_key_cxstr(key), value); } -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxmutstr const &key, @@ -380,15 +362,8 @@ return map->cl->put(map, cx_hash_key_cxstr(key), value); } -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cxMapPut( CxMap *map, const char *key, @@ -397,14 +372,8 @@ return map->cl->put(map, cx_hash_key_str(key), value); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, CxHashKey const &key @@ -412,14 +381,8 @@ return map->cl->get(map, key); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, cxstring const &key @@ -427,14 +390,8 @@ return map->cl->get(map, cx_hash_key_cxstr(key)); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, cxmutstr const &key @@ -442,14 +399,9 @@ return map->cl->get(map, cx_hash_key_cxstr(key)); } -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cxMapGet( const CxMap *map, const char *key @@ -457,195 +409,86 @@ return map->cl->get(map, cx_hash_key_str(key)); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, CxHashKey const &key ) { - (void) map->cl->remove(map, key, true); + return map->cl->remove(map, key, nullptr); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, cxstring const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, cxmutstr const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); } -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key - * @see cxMapRemoveAndGet() - * @see cxMapDetach() - */ -__attribute__((__nonnull__)) -static inline void cxMapRemove( +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxMapRemove( CxMap *map, const char *key ) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); + return map->cl->remove(map, cx_hash_key_str(key), nullptr); } -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( CxMap *map, - CxHashKey const &key + CxHashKey key, + void *targetbuf ) { - (void) map->cl->remove(map, key, false); + return map->cl->remove(map, key, targetbuf); } -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( CxMap *map, - cxstring const &key + cxstring key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(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() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( CxMap *map, - cxmutstr const &key + cxmutstr key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(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() - */ -__attribute__((__nonnull__)) -static inline void cxMapDetach( +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cxMapRemoveAndGet( CxMap *map, - const char *key + const char *key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + 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, @@ -655,14 +498,9 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_cxstr( CxMap *map, cxstring key, @@ -672,14 +510,9 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_mustr( CxMap *map, cxmutstr key, @@ -689,14 +522,10 @@ } /** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * @copydoc cxMapPut() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cx_map_put_str( CxMap *map, const char *key, @@ -708,10 +537,19 @@ /** * Puts a key/value-pair into the map. * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure + * A possible existing value will be overwritten. + * + * If this map is storing pointers, the @p value pointer is written + * to the map. Otherwise, the memory is copied from @p value with + * memcpy(). + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero value on memory allocation failure */ #define cxMapPut(map, key, value) _Generic((key), \ CxHashKey: cx_map_put, \ @@ -722,13 +560,10 @@ (map, key, value) /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get( const CxMap *map, CxHashKey key @@ -737,13 +572,10 @@ } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_cxstr( const CxMap *map, cxstring key @@ -752,13 +584,10 @@ } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_mustr( const CxMap *map, cxmutstr key @@ -767,13 +596,11 @@ } /** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value + * @copydoc cxMapGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cx_map_get_str( const CxMap *map, const char *key @@ -784,9 +611,13 @@ /** * Retrieves a value by using a key. * - * @param map the map - * @param key the key - * @return the value + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @return (@c void*) the value */ #define cxMapGet(map, key) _Generic((key), \ CxHashKey: cx_map_get, \ @@ -797,74 +628,61 @@ (map, key) /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemove() */ -__attribute__((__nonnull__)) -static inline void cx_map_remove( +cx_attr_nonnull +static inline int cx_map_remove( CxMap *map, CxHashKey key ) { - (void) map->cl->remove(map, key, true); + return map->cl->remove(map, key, NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove_cxstr( + CxMap *map, + cxstring key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); } /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemove() */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_cxstr( +cx_attr_nonnull +static inline int cx_map_remove_mustr( CxMap *map, - cxstring key + cxmutstr key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_map_remove_str( + CxMap *map, + const char *key +) { + return map->cl->remove(map, cx_hash_key_str(key), NULL); } /** * Removes a key/value-pair from the map by using the key. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. + * Always invokes the destructors functions, if any, on the removed element. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_str( - CxMap *map, - const char *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. - * - * Always invokes the destructor function, if any, on the removed element. - * If this map is storing pointers and you just want to retrieve the pointer - * without invoking the destructor, use cxMapRemoveAndGet(). - * If you just want to detach the element from the map without invoking the - * destructor or returning the element, use cxMapDetach(). - * - * @param map the map - * @param key the key + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @retval zero success + * @retval non-zero the key was not found + * * @see cxMapRemoveAndGet() - * @see cxMapDetach() */ #define cxMapRemove(map, key) _Generic((key), \ CxHashKey: cx_map_remove, \ @@ -875,177 +693,85 @@ (map, key) /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_detach( - CxMap *map, - CxHashKey key -) { - (void) map->cl->remove(map, key, false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_cxstr( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get( CxMap *map, - cxstring key + CxHashKey key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); -} - -/** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_mustr( - CxMap *map, - cxmutstr key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); + return map->cl->remove(map, key, targetbuf); } /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * @param map the map - * @param key the key + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_str( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_cxstr( CxMap *map, - const char *key + cxstring key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); } /** - * Detaches a key/value-pair from the map by using the key - * without invoking the destructor. - * - * In general, you should only use this function if the map does not own - * the data and there is a valid reference to the data somewhere else - * in the program. In all other cases it is preferable to use - * cxMapRemove() or cxMapRemoveAndGet(). - * - * @param map the map - * @param key the key - * @see cxMapRemove() - * @see cxMapRemoveAndGet() + * @copydoc cxMapRemoveAndGet() */ -#define cxMapDetach(map, key) _Generic((key), \ - CxHashKey: cx_map_detach, \ - cxstring: cx_map_detach_cxstr, \ - cxmutstr: cx_map_detach_mustr, \ - char*: cx_map_detach_str, \ - const char*: cx_map_detach_str) \ - (map, key) +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_mustr( + CxMap *map, + cxmutstr key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} /** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get( +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cx_map_remove_and_get_str( CxMap *map, - CxHashKey key + const char *key, + void *targetbuf ) { - return map->cl->remove(map, key, !map->collection.store_pointer); + return map->cl->remove(map, cx_hash_key_str(key), targetbuf); } /** * Removes a key/value-pair from the map by using the key. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * This function will copy the contents of the removed element + * to the target buffer must be guaranteed to be large enough + * to hold the element (the map's element size). + * The destructor functions, if any, will @em not be called. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * If this map is storing pointers, the element is the pointer itself + * and not the object it points to. * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param targetbuf (@c void*) the buffer where the element shall be copied to + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapStorePointers() + * @see cxMapRemove() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_str( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. - * - * This function can be used when the map is storing pointers, - * in order to retrieve the pointer from the map without invoking - * any destructor function. Sometimes you do not want the pointer - * to be returned - in that case (instead of suppressing the "unused - * result" warning) you can use cxMapDetach(). - * - * If this map is not storing pointers, this function behaves like - * cxMapRemove() and returns \c NULL. - * - * @param map the map - * @param key the key - * @return the stored pointer or \c NULL if either the key is not present - * in the map or the map is not storing pointers - * @see cxMapStorePointers() - * @see cxMapDetach() - */ -#define cxMapRemoveAndGet(map, key) _Generic((key), \ +#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ CxHashKey: cx_map_remove_and_get, \ cxstring: cx_map_remove_and_get_cxstr, \ cxmutstr: cx_map_remove_and_get_mustr, \ char*: cx_map_remove_and_get_str, \ const char*: cx_map_remove_and_get_str) \ - (map, key) + (map, key, targetbuf) #endif // __cplusplus diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/mempool.h --- a/ucx/cx/mempool.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/mempool.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file mempool.h - * \brief Interface for memory pool implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file mempool.h + * @brief Interface for memory pool implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_MEMPOOL_H @@ -76,35 +76,33 @@ typedef struct cx_mempool_s CxMempool; /** + * Deallocates a memory pool and frees the managed memory. + * + * @param pool the memory pool to free + */ +void cxMempoolFree(CxMempool *pool); + +/** * Creates an array-based memory pool with a shared destructor function. * * This destructor MUST NOT free the memory. * * @param capacity the initial capacity of the pool - * @param destr the destructor function to use for allocated memory - * @return the created memory pool or \c NULL if allocation failed + * @param destr optional destructor function to use for allocated memory + * @return the created memory pool or @c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMempoolFree, 1) CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); /** * Creates a basic array-based memory pool. * - * @param capacity the initial capacity of the pool - * @return the created memory pool or \c NULL if allocation failed + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) -static inline CxMempool *cxBasicMempoolCreate(size_t capacity) { - return cxMempoolCreate(capacity, NULL); -} - -/** - * Destroys a memory pool and frees the managed memory. - * - * @param pool the memory pool to destroy - */ -__attribute__((__nonnull__)) -void cxMempoolDestroy(CxMempool *pool); +#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL) /** * Sets the destructor function for a specific allocated memory object. @@ -115,13 +113,24 @@ * @param memory the object allocated in the pool * @param fnc the destructor function */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxMempoolSetDestructor( void *memory, cx_destructor_func fnc ); /** + * Removes the destructor function for a specific allocated memory object. + * + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +void cxMempoolRemoveDestructor(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. @@ -130,11 +139,12 @@ * If that allocation fails, this function will return non-zero. * * @param pool the pool - * @param memory the object allocated in the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) * @param destr the destructor function - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxMempoolRegister( CxMempool *pool, void *memory, diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/printf.h --- a/ucx/cx/printf.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/printf.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file printf.h - * \brief Wrapper for write functions with a printf-like interface. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file printf.h + * @brief Wrapper for write functions with a printf-like interface. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_PRINTF_H @@ -40,6 +40,14 @@ #include "string.h" #include +/** + * Attribute for printf-like functions. + * @param fmt_idx index of the format string parameter + * @param arg_idx index of the first formatting argument + */ +#define cx_attr_printf(fmt_idx, arg_idx) \ + __attribute__((__format__(printf, fmt_idx, arg_idx))) + #ifdef __cplusplus extern "C" { #endif @@ -48,19 +56,21 @@ /** * The maximum string length that fits into stack memory. */ -extern unsigned const cx_printf_sbo_size; +extern const unsigned cx_printf_sbo_size; /** - * A \c fprintf like function which writes the output to a stream by + * A @c fprintf like function which writes the output to a stream by * using a write_func. * * @param stream the stream the data is written to * @param wfc the write function * @param fmt format string * @param ... additional arguments - * @return the total number of bytes written + * @return the total number of bytes written or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4))) +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_printf(3, 4) +cx_attr_cstr_arg(3) int cx_fprintf( void *stream, cx_write_func wfc, @@ -69,17 +79,18 @@ ); /** - * A \c vfprintf like function which writes the output to a stream by + * A @c vfprintf like function which writes the output to a stream by * using a write_func. * * @param stream the stream the data is written to * @param wfc the write function * @param fmt format string * @param ap argument list - * @return the total number of bytes written + * @return the total number of bytes written or an error code from stdlib printf implementation * @see cx_fprintf() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(3) int cx_vfprintf( void *stream, cx_write_func wfc, @@ -88,10 +99,12 @@ ); /** - * A \c asprintf like function which allocates space for a string + * A @c asprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * * @param allocator the CxAllocator used for allocating the string * @param fmt format string @@ -99,7 +112,9 @@ * @return the formatted string * @see cx_strfree_a() */ -__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3))) +cx_attr_nonnull_arg(1, 2) +cx_attr_printf(2, 3) +cx_attr_cstr_arg(2) cxmutstr cx_asprintf_a( const CxAllocator *allocator, const char *fmt, @@ -107,24 +122,28 @@ ); /** - * A \c asprintf like function which allocates space for a string + * A @c asprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * - * @param fmt format string + * @param fmt (@c char*) format string * @param ... additional arguments - * @return the formatted string + * @return (@c cxmutstr) the formatted string * @see cx_strfree() */ #define cx_asprintf(fmt, ...) \ cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) /** -* A \c vasprintf like function which allocates space for a string +* A @c vasprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. * * @param allocator the CxAllocator used for allocating the string * @param fmt format string @@ -132,7 +151,8 @@ * @return the formatted string * @see cx_asprintf_a() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) cxmutstr cx_vasprintf_a( const CxAllocator *allocator, const char *fmt, @@ -140,192 +160,232 @@ ); /** -* A \c vasprintf like function which allocates space for a string +* A @c vasprintf like function which allocates space for a string * the result is written to. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was in error, in which case the string's pointer + * will be @c NULL. * - * @param fmt format string - * @param ap argument list - * @return the formatted string + * @param fmt (@c char*) format string + * @param ap (@c va_list) argument list + * @return (@c cxmutstr) the formatted string * @see cx_asprintf() */ #define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap) /** - * A \c printf like function which writes the output to a CxBuffer. + * A @c printf like function which writes the output to a CxBuffer. * - * @param buffer a pointer to the buffer the data is written to - * @param fmt the format string + * @param buffer (@c CxBuffer*) a pointer to the buffer the data is written to + * @param fmt (@c char*) the format string * @param ... additional arguments - * @return the total number of bytes written - * @see ucx_fprintf() + * @return (@c int) the total number of bytes written or an error code from stdlib printf implementation + * @see cx_fprintf() + * @see cxBufferWrite() */ -#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \ +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) /** - * An \c sprintf like function which reallocates the string when the buffer is not large enough. + * An @c sprintf like function which reallocates the string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * @param str a pointer to the string buffer - * @param len a pointer to the length of the buffer - * @param fmt the format string + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string * @param ... additional arguments - * @return the length of produced string + * @return (@c int) the length of produced string or an error code from stdlib printf implementation */ #define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) /** - * An \c sprintf like function which reallocates the string when the buffer is not large enough. + * An @c sprintf like function which reallocates the string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * \attention The original buffer MUST have been allocated with the same allocator! + * @attention The original buffer MUST have been allocated with the same allocator! * * @param alloc the allocator to use * @param str a pointer to the string buffer * @param len a pointer to the length of the buffer * @param fmt the format string * @param ... additional arguments - * @return the length of produced string + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5))) -int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ); +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_printf(4, 5) +cx_attr_cstr_arg(4) +int cx_sprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + ... +); /** - * An \c sprintf like function which reallocates the string when the buffer is not large enough. + * An @c sprintf like function which reallocates the string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * @param str a pointer to the string buffer - * @param len a pointer to the length of the buffer - * @param fmt the format string - * @param ap argument list - * @return the length of produced string + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation */ #define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) /** - * An \c sprintf like function which reallocates the string when the buffer is not large enough. + * An @c sprintf like function which reallocates the string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated. * - * \attention The original buffer MUST have been allocated with the same allocator! + * @attention The original buffer MUST have been allocated with the same allocator! * * @param alloc the allocator to use * @param str a pointer to the string buffer * @param len a pointer to the length of the buffer * @param fmt the format string * @param ap argument list - * @return the length of produced string + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__)) -int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(4) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +int cx_vsprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + va_list ap +); /** - * An \c sprintf like function which allocates a new string when the buffer is not large enough. + * An @c sprintf like function which allocates a new string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * The location of the resulting string will \em always be stored to \p str. When the buffer - * was sufficiently large, \p buf itself will be stored to the location of \p str. + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * \remark When a new string needed to be allocated, the contents of \p buf will be - * poisoned after the call, because this function tries to produce the string in \p buf, first. + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. * - * @param buf a pointer to the buffer - * @param len a pointer to the length of the buffer - * @param str a pointer to the location - * @param fmt the format string + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string * @param ... additional arguments - * @return the length of produced string + * @return (@c int) the length of produced string or an error code from stdlib printf implementation */ #define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) /** - * An \c sprintf like function which allocates a new string when the buffer is not large enough. + * An @c sprintf like function which allocates a new string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * The location of the resulting string will \em always be stored to \p str. When the buffer - * was sufficiently large, \p buf itself will be stored to the location of \p str. + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * \remark When a new string needed to be allocated, the contents of \p buf will be - * poisoned after the call, because this function tries to produce the string in \p buf, first. + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. * * @param alloc the allocator to use * @param buf a pointer to the buffer * @param len a pointer to the length of the buffer - * @param str a pointer to the location + * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ... additional arguments - * @return the length of produced string + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6))) -int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ); +cx_attr_nonnull_arg(1, 2, 4, 5) +cx_attr_printf(5, 6) +cx_attr_cstr_arg(5) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +cx_attr_access_rw(4) +int cx_sprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + ... +); /** - * An \c sprintf like function which allocates a new string when the buffer is not large enough. + * An @c sprintf like function which allocates a new string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * The location of the resulting string will \em always be stored to \p str. When the buffer - * was sufficiently large, \p buf itself will be stored to the location of \p str. + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated. * - * \remark When a new string needed to be allocated, the contents of \p buf will be - * poisoned after the call, because this function tries to produce the string in \p buf, first. + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. * - * @param buf a pointer to the buffer - * @param len a pointer to the length of the buffer - * @param str a pointer to the location - * @param fmt the format string - * @param ap argument list - * @return the length of produced string + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation */ #define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) /** - * An \c sprintf like function which allocates a new string when the buffer is not large enough. + * An @c sprintf like function which allocates a new string when the buffer is not large enough. * - * The size of the buffer will be updated in \p len when necessary. + * The size of the buffer will be updated in @p len when necessary. * - * The location of the resulting string will \em always be stored to \p str. When the buffer - * was sufficiently large, \p buf itself will be stored to the location of \p str. + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated. * - * \remark When a new string needed to be allocated, the contents of \p buf will be - * poisoned after the call, because this function tries to produce the string in \p buf, first. + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. * * @param alloc the allocator to use * @param buf a pointer to the buffer * @param len a pointer to the length of the buffer - * @param str a pointer to the location + * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ap argument list - * @return the length of produced string + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__)) -int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(5) +int cx_vsprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +); #ifdef __cplusplus diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/properties.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/properties.h Mon Jan 06 22:22:55 2025 +0100 @@ -0,0 +1,644 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * @brief Interface for parsing data from properties files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "common.h" +#include "string.h" +#include "map.h" +#include "buffer.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Configures the expected characters for the properties parser. + */ +struct cx_properties_config_s { + /** + * The key/value delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The character, when appearing at the end of a line, continues that line. + * This is '\' by default. + */ + // char continuation; // TODO: line continuation in properties + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; +}; + +/** + * Typedef for the properties config. + */ +typedef struct cx_properties_config_s CxPropertiesConfig; + +/** + * Default properties configuration. + */ +extern const CxPropertiesConfig cx_properties_config_default; + +/** + * Status codes for the properties interface. + */ +enum cx_properties_status { + /** + * Everything is fine. + */ + CX_PROPERTIES_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_PROPERTIES_NO_DATA, + /** + * The input ends unexpectedly. + * + * This either happens when the last line does not terminate with a line + * break, or when the input ends with a parsed key but no value. + */ + CX_PROPERTIES_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_PROPERTIES_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_PROPERTIES_OK, + /** + * Input buffer is @c NULL. + */ + CX_PROPERTIES_NULL_INPUT, + /** + * The line contains a delimiter, but no key. + */ + CX_PROPERTIES_INVALID_EMPTY_KEY, + /** + * The line contains data, but no delimiter. + */ + CX_PROPERTIES_INVALID_MISSING_DELIMITER, + /** + * More internal buffer was needed, but could not be allocated. + */ + CX_PROPERTIES_BUFFER_ALLOC_FAILED, + /** + * Initializing the properties source failed. + * + * @see cx_properties_read_init_func + */ + CX_PROPERTIES_READ_INIT_FAILED, + /** + * Reading from a properties source failed. + * + * @see cx_properties_read_func + */ + CX_PROPERTIES_READ_FAILED, + /** + * Sinking a k/v-pair failed. + * + * @see cx_properties_sink_func + */ + CX_PROPERTIES_SINK_FAILED, +}; + +/** + * Typedef for the properties status enum. + */ +typedef enum cx_properties_status CxPropertiesStatus; + +/** + * Interface for working with properties data. + */ +struct cx_properties_s { + /** + * The configuration. + */ + CxPropertiesConfig config; + + /** + * The text input buffer. + */ + CxBuffer input; + + /** + * Internal buffer. + */ + CxBuffer buffer; +}; + +/** + * Typedef for the properties interface. + */ +typedef struct cx_properties_s CxProperties; + + +/** + * Typedef for a properties sink. + */ +typedef struct cx_properties_sink_s CxPropertiesSink; + +/** + * A function that consumes a k/v-pair in a sink. + * + * The sink could be e.g. a map and the sink function would be calling + * a map function to store the k/v-pair. + * + * @param prop the properties interface that wants to sink a k/v-pair + * @param sink the sink + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero sinking the k/v-pair failed + */ +cx_attr_nonnull +typedef int(*cx_properties_sink_func)( + CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +); + +/** + * Defines a sink for k/v-pairs. + */ +struct cx_properties_sink_s { + /** + * The sink object. + */ + void *sink; + /** + * Optional custom data. + */ + void *data; + /** + * A function for consuming k/v-pairs into the sink. + */ + cx_properties_sink_func sink_func; +}; + + +/** + * Typedef for a properties source. + */ +typedef struct cx_properties_source_s CxPropertiesSource; + +/** + * A function that reads data from a source. + * + * When the source is depleted, implementations SHALL provide an empty + * string in the @p target and return zero. + * A non-zero return value is only permitted in case of an error. + * + * The meaning of the optional parameters is implementation-dependent. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @param target a string buffer where the read data shall be stored + * @retval zero success + * @retval non-zero reading the data failed + */ +cx_attr_nonnull +typedef int(*cx_properties_read_func)( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +); + +/** + * A function that may initialize additional memory for the source. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @retval zero initialization was successful + * @retval non-zero otherwise + */ +cx_attr_nonnull +typedef int(*cx_properties_read_init_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * A function that cleans memory initialized by the read_init_func. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + */ +cx_attr_nonnull +typedef void(*cx_properties_read_clean_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * Defines a properties source. + */ +struct cx_properties_source_s { + /** + * The source object. + * + * For example a file stream or a string. + */ + void *src; + /** + * Optional additional data pointer. + */ + void *data_ptr; + /** + * Optional size information. + */ + size_t data_size; + /** + * A function that reads data from the source. + */ + cx_properties_read_func read_func; + /** + * Optional function that may prepare the source for reading data. + */ + cx_properties_read_init_func read_init_func; + /** + * Optional function that cleans additional memory allocated by the + * read_init_func. + */ + cx_properties_read_clean_func read_clean_func; +}; + +/** + * Initialize a properties interface. + * + * @param prop the properties interface + * @param config the properties configuration + * @see cxPropertiesInitDefault() + */ +cx_attr_nonnull +void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); + +/** + * Destroys the properties interface. + * + * @note Even when you are certain that you did not use the interface in a + * way that caused a memory allocation, you should call this function anyway. + * Future versions of the library might add features that need additional memory + * and you really don't want to search the entire code where you might need + * add call to this function. + * + * @param prop the properties interface + */ +cx_attr_nonnull +void cxPropertiesDestroy(CxProperties *prop); + +/** + * Destroys and re-initializes the properties interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param prop the properties interface + */ +cx_attr_nonnull +static inline void cxPropertiesReset(CxProperties *prop) { + CxPropertiesConfig config = prop->config; + cxPropertiesDestroy(prop); + cxPropertiesInit(prop, config); +} + +/** + * Initialize a properties parser with the default configuration. + * + * @param prop (@c CxProperties*) the properties interface + * @see cxPropertiesInit() + */ +#define cxPropertiesInitDefault(prop) \ + cxPropertiesInit(prop, cx_properties_config_default) + +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @remark The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param buf a pointer to the data + * @param len the length of the data + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +); + +#ifdef __cplusplus +} // extern "C" +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxPropertiesFill( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @attention The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFilln() + */ +#define cxPropertiesFill(prop, str) _Generic((str), \ + cxstring: cx_properties_fill_cxstr, \ + cxmutstr: cx_properties_fill_mutstr, \ + char*: cx_properties_fill_str, \ + const char*: cx_properties_fill_str) \ + (prop, str) + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_cxstr( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_mutstr( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_properties_fill_str( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} +#endif + +/** + * Specifies stack memory that shall be used as internal buffer. + * + * @param prop the properties interface + * @param buf a pointer to stack memory + * @param capacity the capacity of the stack memory + */ +cx_attr_nonnull +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +); + +/** + * Retrieves the next key/value-pair. + * + * This function returns zero as long as there are key/value-pairs found. + * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned. + * + * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is + * returned, and you can add more data with #cxPropertiesFill(). + * + * @remark The incomplete line will be stored in an internal buffer, which is + * allocated on the heap, by default. If you want to avoid allocations, + * you can specify sufficient space with cxPropertiesUseStack() after + * initialization with cxPropertiesInit(). + * + * @attention The returned strings will point into a buffer that might not be + * available later. It is strongly recommended to copy the strings for further + * use. + * + * @param prop the properties interface + * @param key a pointer to the cxstring that shall contain the property name + * @param value a pointer to the cxstring that shall contain the property value + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer + * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete + * (fill more data and try again) + * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +cx_attr_nodiscard +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +); + +/** + * Creates a properties sink for an UCX map. + * + * The values stored in the map will be pointers to strings allocated + * by #cx_strdup_a(). + * The default stdlib allocator will be used, unless you specify a custom + * allocator in the optional @c data of the sink. + * + * @param map the map that shall consume the k/v-pairs. + * @return the sink + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxPropertiesSink cxPropertiesMapSink(CxMap *map); + +/** + * Creates a properties source based on an UCX string. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nodiscard +CxPropertiesSource cxPropertiesStringSource(cxstring str); + +/** + * Creates a properties source based on C string with the specified length. + * + * @param str the string + * @param len the length + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1, 2) +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); + +/** + * Creates a properties source based on a C string. + * + * The length will be determined with strlen(), so the string MUST be + * zero-terminated. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +CxPropertiesSource cxPropertiesCstrSource(const char *str); + +/** + * Creates a properties source based on an FILE. + * + * @param file the file + * @param chunk_size how many bytes may be read in one operation + * + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1) +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); + + +/** + * Loads properties data from a source and transfers it to a sink. + * + * This function tries to read as much data from the source as possible. + * When the source was completely consumed and at least on k/v-pair was found, + * the return value will be #CX_PROPERTIES_NO_ERROR. + * When the source was consumed but no k/v-pairs were found, the return value + * will be #CX_PROPERTIES_NO_DATA. + * The other result codes apply, according to their description. + * + * @param prop the properties interface + * @param sink the sink + * @param source the source + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed + * @retval CX_PROPERTIES_READ_FAILED reading from the source failed + * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed + * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_PROPERTIES_H diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/streams.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/streams.h Mon Jan 06 22:22:55 2025 +0100 @@ -0,0 +1,135 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file streams.h + * + * @brief Utility functions for data streams. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_STREAMS_H +#define UCX_STREAMS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can + * set this to zero to let the implementation decide + * @param n the maximum number of bytes that shall be copied. + * If this is larger than @p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_access_w(5) +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is + * @c NULL you can set this to zero to let the implementation decide + * @return total number of bytes copied + */ +#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +cx_attr_nonnull +cx_attr_access_r(1) +cx_attr_access_w(2) +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_STREAMS_H diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/string.h --- a/ucx/cx/string.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/string.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file string.h - * \brief Strings that know their length. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file string.h + * @brief Strings that know their length. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_STRING_H @@ -42,7 +42,7 @@ /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ -extern unsigned const cx_strstr_sbo_size; +extern const unsigned cx_strstr_sbo_size; /** * The UCX string structure. @@ -50,7 +50,7 @@ struct cx_mutstr_s { /** * A pointer to the string. - * \note The string is not necessarily \c NULL terminated. + * @note The string is not necessarily @c NULL terminated. * Always use the length. */ char *ptr; @@ -69,7 +69,7 @@ struct cx_string_s { /** * A pointer to the immutable string. - * \note The string is not necessarily \c NULL terminated. + * @note The string is not necessarily @c NULL terminated. * Always use the length. */ const char *ptr; @@ -148,7 +148,7 @@ /** * A literal initializer for an UCX string structure. * - * The argument MUST be a string (const char*) \em literal. + * The argument MUST be a string (const char*) @em literal. * * @param literal the string literal */ @@ -160,9 +160,9 @@ /** * Wraps a mutable string that must be zero-terminated. * - * The length is implicitly inferred by using a call to \c strlen(). + * The length is implicitly inferred by using a call to @c strlen(). * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a constant string, use cx_str(). @@ -172,26 +172,29 @@ * * @see cx_mutstrn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) cxmutstr cx_mutstr(char *cstring); /** * Wraps a string that does not need to be zero-terminated. * - * The argument may be \c NULL if the length is zero. + * The argument may be @c NULL if the length is zero. * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a constant string, use cx_strn(). * - * @param cstring the string to wrap (or \c NULL, only if the length is zero) + * @param cstring the string to wrap (or @c NULL, only if the length is zero) * @param length the length of the string * @return the wrapped string * * @see cx_mutstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_rw(1, 2) cxmutstr cx_mutstrn( char *cstring, size_t length @@ -200,9 +203,9 @@ /** * Wraps a string that must be zero-terminated. * - * The length is implicitly inferred by using a call to \c strlen(). + * The length is implicitly inferred by using a call to @c strlen(). * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a non-constant string, use cx_mutstr(). @@ -212,72 +215,112 @@ * * @see cx_strn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) cxstring cx_str(const char *cstring); /** * Wraps a string that does not need to be zero-terminated. * - * The argument may be \c NULL if the length is zero. + * The argument may be @c NULL if the length is zero. * - * \note the wrapped string will share the specified pointer to the string. + * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a non-constant string, use cx_mutstrn(). * - * @param cstring the string to wrap (or \c NULL, only if the length is zero) + * @param cstring the string to wrap (or @c NULL, only if the length is zero) * @param length the length of the string * @return the wrapped string * * @see cx_str() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) cxstring cx_strn( const char *cstring, size_t length ); +#ifdef __cplusplus +} // extern "C" +cx_attr_nodiscard +static inline cxstring cx_strcast(cxmutstr str) { + return cx_strn(str.ptr, str.length); +} +cx_attr_nodiscard +static inline cxstring cx_strcast(cxstring str) { + return str; +} +extern "C" { +#else +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_m(cxmutstr str) { + return (cxstring) {str.ptr, str.length}; +} +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_c(cxstring str) { + return str; +} + /** * Casts a mutable string to an immutable string. * -* \note This is not seriously a cast. Instead you get a copy +* Does nothing for already immutable strings. +* +* @note This is not seriously a cast. Instead, you get a copy * of the struct with the desired pointer type. Both structs still * point to the same location, though! * -* @param str the mutable string to cast -* @return an immutable copy of the string pointer +* @param str (@c cxstring or @c cxmutstr) the string to cast +* @return (@c cxstring) an immutable copy of the string pointer */ -__attribute__((__warn_unused_result__)) -cxstring cx_strcast(cxmutstr str); +#define cx_strcast(str) _Generic((str), \ + cxmutstr: cx_strcast_m, \ + cxstring: cx_strcast_c) \ + (str) +#endif /** - * Passes the pointer in this string to \c free(). + * Passes the pointer in this string to @c free(). * - * The pointer in the struct is set to \c NULL and the length is set to zero. + * The pointer in the struct is set to @c NULL and the length is set to zero. * - * \note There is no implementation for cxstring, because it is unlikely that + * @note There is no implementation for cxstring, because it is unlikely that * you ever have a const char* you are really supposed to free. * If you encounter such situation, you should double-check your code. * * @param str the string to free */ -__attribute__((__nonnull__)) void cx_strfree(cxmutstr *str); /** * Passes the pointer in this string to the allocators free function. * - * The pointer in the struct is set to \c NULL and the length is set to zero. + * The pointer in the struct is set to @c NULL and the length is set to zero. * - * \note There is no implementation for cxstring, because it is unlikely that + * @note There is no implementation for cxstring, because it is unlikely that * you ever have a const char* you are really supposed to free. * If you encounter such situation, you should double-check your code. * * @param alloc the allocator * @param str the string to free */ -__attribute__((__nonnull__)) +cx_attr_nonnull_arg(1) void cx_strfree_a( const CxAllocator *alloc, cxmutstr *str @@ -285,15 +328,17 @@ /** * Returns the accumulated length of all specified strings. + * + * If this sum overflows, errno is set to EOVERFLOW. * - * \attention if the count argument is larger than the number of the + * @attention if the count argument is larger than the number of the * specified strings, the behavior is undefined. * * @param count the total number of specified strings * @param ... all strings * @return the accumulated length of all strings */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard size_t cx_strlen( size_t count, ... @@ -303,21 +348,26 @@ * Concatenates strings. * * The resulting string will be allocated by the specified allocator. - * So developers \em must pass the return value to cx_strfree_a() eventually. + * So developers @em must pass the return value to cx_strfree_a() eventually. * - * If \p str already contains a string, the memory will be reallocated and + * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * \note It is guaranteed that there is only one allocation. + * If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * * @param alloc the allocator to use * @param str the string the other strings shall be concatenated to * @param count the number of the other following strings to concatenate - * @param ... all other strings + * @param ... all other UCX strings * @return the concatenated string */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strcat_ma( const CxAllocator *alloc, cxmutstr str, @@ -329,15 +379,19 @@ * Concatenates strings and returns a new string. * * The resulting string will be allocated by the specified allocator. - * So developers \em must pass the return value to cx_strfree_a() eventually. + * So developers @em must pass the return value to cx_strfree_a() eventually. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param alloc the allocator to use - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param alloc (@c CxAllocator*) the allocator to use + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_a(alloc, count, ...) \ cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) @@ -345,15 +399,19 @@ /** * Concatenates strings and returns a new string. * - * The resulting string will be allocated by standard \c malloc(). - * So developers \em must pass the return value to cx_strfree() eventually. + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat(count, ...) \ cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) @@ -361,19 +419,23 @@ /** * Concatenates strings. * - * The resulting string will be allocated by standard \c malloc(). - * So developers \em must pass the return value to cx_strfree() eventually. + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. * - * If \p str already contains a string, the memory will be reallocated and + * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * \note It is guaranteed that there is only one allocation. +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. * It is also guaranteed that the returned string is zero-terminated. * - * @param str the string the other strings shall be concatenated to - * @param count the number of the other following strings to concatenate - * @param ... all other strings - * @return the concatenated string + * @param str (@c cxmutstr) the string the other strings shall be concatenated to + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other strings + * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_m(str, count, ...) \ cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) @@ -381,19 +443,19 @@ /** * Returns a substring starting at the specified location. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubsl() * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strsubs( cxstring string, size_t start @@ -402,23 +464,23 @@ /** * Returns a substring starting at the specified location. * - * The returned string will be limited to \p length bytes or the number - * of bytes available in \p string, whichever is smaller. + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring * @param length the maximum length of the returned string - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubs() * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strsubsl( cxstring string, size_t start, @@ -428,19 +490,19 @@ /** * Returns a substring starting at the specified location. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubsl_m() * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strsubs_m( cxmutstr string, size_t start @@ -449,23 +511,23 @@ /** * Returns a substring starting at the specified location. * - * The returned string will be limited to \p length bytes or the number - * of bytes available in \p string, whichever is smaller. + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. * - * \attention the new string references the same memory area as the - * input string and is usually \em not zero-terminated. + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. * Use cx_strdup() to get a copy. * * @param string input string * @param start start location of the substring * @param length the maximum length of the returned string - * @return a substring of \p string starting at \p start + * @return a substring of @p string starting at @p start * * @see cx_strsubs_m() * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strsubsl_m( cxmutstr string, size_t start, @@ -480,11 +542,11 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the first location of \p chr + * @return a substring starting at the first location of @p chr * * @see cx_strchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strchr( cxstring string, int chr @@ -498,11 +560,11 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the first location of \p chr + * @return a substring starting at the first location of @p chr * * @see cx_strchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strchr_m( cxmutstr string, int chr @@ -516,11 +578,11 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the last location of \p chr + * @return a substring starting at the last location of @p chr * * @see cx_strrchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strrchr( cxstring string, int chr @@ -534,11 +596,11 @@ * * @param string the string where to locate the character * @param chr the character to locate - * @return a substring starting at the last location of \p chr + * @return a substring starting at the last location of @p chr * * @see cx_strrchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strrchr_m( cxmutstr string, int chr @@ -548,19 +610,19 @@ * Returns a substring starting at the location of the first occurrence of the * specified string. * - * If \p haystack does not contain \p needle, an empty string is returned. + * If @p haystack does not contain @p needle, an empty string is returned. * - * If \p needle is an empty string, the complete \p haystack is + * If @p needle is an empty string, the complete @p haystack is * returned. * * @param haystack the string to be scanned * @param needle string containing the sequence of characters to match * @return a substring starting at the first occurrence of - * \p needle, or an empty string, if the sequence is not + * @p needle, or an empty string, if the sequence is not * contained * @see cx_strstr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strstr( cxstring haystack, cxstring needle @@ -570,19 +632,19 @@ * Returns a substring starting at the location of the first occurrence of the * specified string. * - * If \p haystack does not contain \p needle, an empty string is returned. + * If @p haystack does not contain @p needle, an empty string is returned. * - * If \p needle is an empty string, the complete \p haystack is + * If @p needle is an empty string, the complete @p haystack is * returned. * * @param haystack the string to be scanned * @param needle string containing the sequence of characters to match * @return a substring starting at the first occurrence of - * \p needle, or an empty string, if the sequence is not + * @p needle, or an empty string, if the sequence is not * contained * @see cx_strstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strstr_m( cxmutstr haystack, cxstring needle @@ -591,16 +653,18 @@ /** * Splits a given string using a delimiter string. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least \p limit length + * @param output a pre-allocated array of at least @p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) size_t cx_strsplit( cxstring string, cxstring delim, @@ -611,13 +675,13 @@ /** * Splits a given string using a delimiter string. * - * The array pointed to by \p output will be allocated by \p allocator. + * The array pointed to by @p output will be allocated by @p allocator. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * - * \attention If allocation fails, the \c NULL pointer will be written to - * \p output and the number returned will be zero. + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. * * @param allocator the allocator to use for allocating the resulting array * @param string the string to split @@ -627,7 +691,9 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) size_t cx_strsplit_a( const CxAllocator *allocator, cxstring string, @@ -640,16 +706,18 @@ /** * Splits a given string using a delimiter string. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least \p limit length + * @param output a pre-allocated array of at least @p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) size_t cx_strsplit_m( cxmutstr string, cxstring delim, @@ -660,13 +728,13 @@ /** * Splits a given string using a delimiter string. * - * The array pointed to by \p output will be allocated by \p allocator. + * The array pointed to by @p output will be allocated by @p allocator. * - * \note The resulting array contains strings that point to the source - * \p string. Use cx_strdup() to get copies. + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. * - * \attention If allocation fails, the \c NULL pointer will be written to - * \p output and the number returned will be zero. + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. * * @param allocator the allocator to use for allocating the resulting array * @param string the string to split @@ -676,7 +744,9 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) size_t cx_strsplit_ma( const CxAllocator *allocator, cxmutstr string, @@ -690,10 +760,10 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard int cx_strcmp( cxstring s1, cxstring s2 @@ -704,10 +774,10 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal ignoring case + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard int cx_strcasecmp( cxstring s1, cxstring s2 @@ -720,10 +790,11 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cx_strcmp_p( const void *s1, const void *s2 @@ -736,10 +807,11 @@ * * @param s1 the first string * @param s2 the second string - * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger - * than \p s2, zero if both strings equal ignoring case + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cx_strcasecmp_p( const void *s1, const void *s2 @@ -749,16 +821,17 @@ /** * Creates a duplicate of the specified string. * - * The new string will contain a copy allocated by \p allocator. + * The new string will contain a copy allocated by @p allocator. * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * * @param allocator the allocator to use * @param string the string to duplicate * @return a duplicate of the string * @see cx_strdup() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strdup_a( const CxAllocator *allocator, cxstring string @@ -768,12 +841,12 @@ * Creates a duplicate of the specified string. * * The new string will contain a copy allocated by standard - * \c malloc(). So developers \em must pass the return value to cx_strfree(). + * @c malloc(). So developers @em must pass the return value to cx_strfree(). * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * - * @param string the string to duplicate - * @return a duplicate of the string + * @param string (@c cxstring) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_a() */ #define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) @@ -782,13 +855,13 @@ /** * Creates a duplicate of the specified string. * - * The new string will contain a copy allocated by \p allocator. + * The new string will contain a copy allocated by @p allocator. * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * - * @param allocator the allocator to use - * @param string the string to duplicate - * @return a duplicate of the string + * @param allocator (@c CxAllocator*) the allocator to use + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_m() */ #define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string)) @@ -797,12 +870,12 @@ * Creates a duplicate of the specified string. * * The new string will contain a copy allocated by standard - * \c malloc(). So developers \em must pass the return value to cx_strfree(). + * @c malloc(). So developers @em must pass the return value to cx_strfree(). * - * \note The returned string is guaranteed to be zero-terminated. + * @note The returned string is guaranteed to be zero-terminated. * - * @param string the string to duplicate - * @return a duplicate of the string + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_ma() */ #define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string)) @@ -810,25 +883,25 @@ /** * Omits leading and trailing spaces. * - * \note the returned string references the same memory, thus you - * must \em not free the returned memory. + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. * * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strtrim(cxstring string); /** * Omits leading and trailing spaces. * - * \note the returned string references the same memory, thus you - * must \em not free the returned memory. + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. * * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strtrim_m(cxmutstr string); /** @@ -836,10 +909,10 @@ * * @param string the string to check * @param prefix the prefix the string should have - * @return \c true, if and only if the string has the specified prefix, - * \c false otherwise + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strprefix( cxstring string, cxstring prefix @@ -850,10 +923,10 @@ * * @param string the string to check * @param suffix the suffix the string should have - * @return \c true, if and only if the string has the specified suffix, - * \c false otherwise + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strsuffix( cxstring string, cxstring suffix @@ -864,10 +937,10 @@ * * @param string the string to check * @param prefix the prefix the string should have - * @return \c true, if and only if the string has the specified prefix, - * \c false otherwise + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strcaseprefix( cxstring string, cxstring prefix @@ -878,10 +951,10 @@ * * @param string the string to check * @param suffix the suffix the string should have - * @return \c true, if and only if the string has the specified suffix, - * \c false otherwise + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strcasesuffix( cxstring string, cxstring suffix @@ -911,9 +984,9 @@ * Replaces a pattern in a string with another string. * * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. + * Replaces at most @p replmax occurrences. * - * The returned string will be allocated by \p allocator and is guaranteed + * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, @@ -926,7 +999,8 @@ * @param replmax maximum number of replacements * @return the resulting string after applying the replacements */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, @@ -939,19 +1013,19 @@ * Replaces a pattern in a string with another string. * * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. + * Replaces at most @p replmax occurrences. * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @param replmax maximum number of replacements - * @return the resulting string after applying the replacements + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @param replmax (@c size_t) maximum number of replacements + * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplacen(str, pattern, replacement, replmax) \ cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax) @@ -961,17 +1035,17 @@ * * The pattern is taken literally and is no regular expression. * - * The returned string will be allocated by \p allocator and is guaranteed + * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param allocator the allocator to use - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @return the resulting string after applying the replacements + * @param allocator (@c CxAllocator*) the allocator to use + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace_a(allocator, str, pattern, replacement) \ cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX) @@ -980,18 +1054,18 @@ * Replaces a pattern in a string with another string. * * The pattern is taken literally and is no regular expression. - * Replaces at most \p replmax occurrences. + * Replaces at most @p replmax occurrences. * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str the string where replacements should be applied - * @param pattern the pattern to search for - * @param replacement the replacement string - * @return the resulting string after applying the replacements + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace(str, pattern, replacement) \ cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX) @@ -1004,7 +1078,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok( cxstring str, cxstring delim, @@ -1019,7 +1093,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok_m( cxmutstr str, cxstring delim, @@ -1036,7 +1110,9 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -1054,7 +1130,9 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) bool cx_strtok_next_m( CxStrtokCtx *ctx, cxmutstr *token @@ -1067,13 +1145,407 @@ * @param delim array of more delimiters * @param count number of elements in the array */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(2, 3) void cx_strtok_delim( CxStrtokCtx *ctx, const cxstring *delim, size_t count ); +/* ------------------------------------------------------------------------- * + * string to number conversion functions * + * ------------------------------------------------------------------------- */ + +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep); + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep); + +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep); + +#ifndef CX_STR_IMPLEMENTATION +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc(cx_strcast(str), output, base, groupsep) +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtouz_lc(str, output, base, groupsep) cx_strtouz_lc(cx_strcast(str), output, base, groupsep) + +/** + * @copydoc cx_strtouz() + */ +#define cx_strtos(str, output, base) cx_strtos_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi(str, output, base) cx_strtoi_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtol(str, output, base) cx_strtol_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoll(str, output, base) cx_strtoll_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi8(str, output, base) cx_strtoi8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi16(str, output, base) cx_strtoi16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi32(str, output, base) cx_strtoi32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi64(str, output, base) cx_strtoi64_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoz(str, output, base) cx_strtoz_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtous(str, output, base) cx_strtous_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou(str, output, base) cx_strtou_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoul(str, output, base) cx_strtoul_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoull(str, output, base) cx_strtoull_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou8(str, output, base) cx_strtou8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou16(str, output, base) cx_strtou16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou32(str, output, base) cx_strtou32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou64(str, output, base) cx_strtou64_lc(str, output, base, ",") +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtouz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtouz(str, output, base) cx_strtouz_lc(str, output, base, ",") + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc(cx_strcast(str), output, decsep, groupsep) +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc(cx_strcast(str), output, decsep, groupsep) + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof(str, output) cx_strtof_lc(str, output, '.', ",") +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod(str, output) cx_strtod_lc(str, output, '.', ",") + +#endif #ifdef __cplusplus } // extern "C" diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/test.h --- a/ucx/cx/test.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/test.h Mon Jan 06 22:22:55 2025 +0100 @@ -35,13 +35,13 @@ * * **** IN HEADER FILE: **** * - *

+ * 
  * CX_TEST(function_name);
  * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
- * 
+ * * * **** IN SOURCE FILE: **** - *
+ * 
  * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
  *   // tests with CX_TEST_ASSERT()
  * }
@@ -54,7 +54,7 @@
  *   }
  *   // cleanup of memory here
  * }
- * 
+ * * * @attention Do not call own functions within a test, that use * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). @@ -67,7 +67,8 @@ #ifndef UCX_TEST_H #define UCX_TEST_H -#include +#include "common.h" + #include #include #include @@ -86,23 +87,10 @@ #define __FUNCTION__ __func__ #endif -// #if !defined(__clang__) && __GNUC__ > 3 #pragma GCC diagnostic ignored "-Wclobbered" #endif -#ifndef UCX_COMMON_H -/** - * Function pointer compatible with fwrite-like functions. - */ -typedef size_t (*cx_write_func)( - const void *, - size_t, - size_t, - void * -); -#endif // UCX_COMMON_H - /** Type for the CxTestSuite. */ typedef struct CxTestSuite CxTestSuite; @@ -148,6 +136,10 @@ * @param name optional name of the suite * @return a new test suite */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_malloc static inline CxTestSuite* cx_test_suite_new(const char *name) { CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); if (suite != NULL) { @@ -161,10 +153,12 @@ } /** - * Destroys a test suite. - * @param suite the test suite to destroy + * Deallocates a test suite. + * + * @param suite the test suite to free */ static inline void cx_test_suite_free(CxTestSuite* suite) { + if (suite == NULL) return; CxTestSet *l = suite->tests; while (l != NULL) { CxTestSet *e = l; @@ -179,8 +173,10 @@ * * @param suite the suite, the test function shall be added to * @param test the test function to register - * @return zero on success or non-zero on failure + * @retval zero success + * @retval non-zero failure */ +cx_attr_nonnull static inline int cx_test_register(CxTestSuite* suite, CxTest test) { CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); if (t) { @@ -205,8 +201,9 @@ * Runs a test suite and writes the test log to the specified stream. * @param suite the test suite to run * @param out_target the target buffer or file to write the output to - * @param out_writer the write function writing to \p out_target + * @param out_writer the write function writing to @p out_target */ +cx_attr_nonnull static inline void cx_test_run(CxTestSuite *suite, void *out_target, cx_write_func out_writer) { if (suite->name == NULL) { @@ -233,14 +230,14 @@ /** * Runs a test suite and writes the test log to the specified FILE stream. - * @param suite the test suite to run - * @param file the target file to write the output to + * @param suite (@c CxTestSuite*) the test suite to run + * @param file (@c FILE*) the target file to write the output to */ #define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite) /** * Runs a test suite and writes the test log to stdout. - * @param suite the test suite to run + * @param suite (@c CxTestSuite*) the test suite to run */ #define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout) @@ -255,6 +252,17 @@ /** * Defines the scope of a test. + * + * @code + * CX_TEST(my_test_name) { + * // setup code + * CX_TEST_DO { + * // your tests go here + * } + * // tear down code + * } + * @endcode + * * @attention Any CX_TEST_ASSERT() calls must be performed in scope of * #CX_TEST_DO. */ @@ -272,8 +280,8 @@ * If the assertion is correct, the test carries on. If the assertion is not * correct, the specified message (terminated by a dot and a line break) is * written to the test suites output stream. - * @param condition the condition to check - * @param message the message that shall be printed out on failure + * @param condition (@c bool) the condition to check + * @param message (@c char*) the message that shall be printed out on failure */ #define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \ const char *_assert_msg_ = message; \ @@ -288,7 +296,7 @@ * If the assertion is correct, the test carries on. If the assertion is not * correct, the specified message (terminated by a dot and a line break) is * written to the test suites output stream. - * @param condition the condition to check + * @param condition (@c bool) the condition to check */ #define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed") diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/cx/tree.h --- a/ucx/cx/tree.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/cx/tree.h Mon Jan 06 22:22:55 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file tree.h - * \brief Interface for tree implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @file tree.h + * @brief Interface for tree implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_TREE_H @@ -138,7 +138,7 @@ */ size_t depth; /** - * The next element in the queue or \c NULL. + * The next element in the queue or @c NULL. */ struct cx_tree_visitor_queue_s *next; }; @@ -209,7 +209,7 @@ * Releases internal memory of the given tree iterator. * @param iter the iterator */ - __attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { free(iter->stack); iter->stack = NULL; @@ -219,7 +219,7 @@ * Releases internal memory of the given tree visitor. * @param visitor the visitor */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { struct cx_tree_visitor_queue_s *q = visitor->queue_next; while (q != NULL) { @@ -233,7 +233,7 @@ * Advises the iterator to skip the subtree below the current node and * also continues the current loop. * - * @param iterator the iterator + * @param iterator (@c CxTreeIterator) the iterator */ #define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue @@ -241,7 +241,7 @@ * Advises the visitor to skip the subtree below the current node and * also continues the current loop. * - * @param visitor the visitor + * @param visitor (@c CxTreeVisitor) the visitor */ #define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor) @@ -249,7 +249,7 @@ * Links a node to a (new) parent. * * If the node has already a parent, it is unlinked, first. - * If the parent has children already, the node is \em appended to the list + * If the parent has children already, the node is @em appended to the list * of all currently existing children. * * @param parent the parent node @@ -258,14 +258,14 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @see cx_tree_unlink() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_link( - void *restrict parent, - void *restrict node, + void *parent, + void *node, ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, @@ -283,11 +283,11 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @see cx_tree_link() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_unlink( void *node, ptrdiff_t loc_parent, @@ -298,14 +298,21 @@ ); /** + * Macro that can be used instead of the magic value for infinite search depth. + */ +#define CX_TREE_SEARCH_INFINITE_DEPTH 0 + +/** * Function pointer for a search function. * - * A function of this kind shall check if the specified \p node - * contains the given \p data or if one of the children might contain + * A function of this kind shall check if the specified @p node + * contains the given @p data or if one of the children might contain * the data. * * The function should use the returned integer to indicate how close the * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. * * For example if a tree stores file path information, a node that is * describing a parent directory of a filename that is searched, shall @@ -321,18 +328,21 @@ * positive if one of the children might contain the data, * negative if neither the node, nor the children contains the data */ +cx_attr_nonnull typedef int (*cx_tree_search_data_func)(const void *node, const void *data); /** * Function pointer for a search function. * - * A function of this kind shall check if the specified \p node - * contains the same \p data as \p new_node or if one of the children might + * A function of this kind shall check if the specified @p node + * contains the same @p data as @p new_node or if one of the children might * contain the data. * * The function should use the returned integer to indicate how close the * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. * * For example if a tree stores file path information, a node that is * describing a parent directory of a filename that is searched, shall @@ -344,10 +354,11 @@ * @param node the node that is currently investigated * @param new_node a new node with the information which is searched * - * @return 0 if \p node contains the same data as \p new_node, + * @return 0 if @p node contains the same data as @p new_node, * positive if one of the children might contain the data, * negative if neither the node, nor the children contains the data */ +cx_attr_nonnull typedef int (*cx_tree_search_func)(const void *node, const void *new_node); /** @@ -359,11 +370,12 @@ * * Depending on the tree structure it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node - * with the best match according to the \p sfunc (meaning: the return value of - * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary * node matching the criteria is returned. * * @param root the root node + * @param depth the maximum depth (zero=indefinite, one=just root) * @param data the data to search for * @param sfunc the search function * @param result where the result shall be stored @@ -373,9 +385,11 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search_data( const void *root, + size_t depth, const void *data, cx_tree_search_data_func sfunc, void **result, @@ -392,11 +406,12 @@ * * Depending on the tree structure it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node - * with the best match according to the \p sfunc (meaning: the return value of - * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary * node matching the criteria is returned. * * @param root the root node +* @param depth the maximum depth (zero=indefinite, one=just root) * @param node the node to search for * @param sfunc the search function * @param result where the result shall be stored @@ -406,9 +421,11 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search( const void *root, + size_t depth, const void *node, cx_tree_search_func sfunc, void **result, @@ -436,6 +453,7 @@ * @return the new tree iterator * @see cxTreeIteratorDispose() */ +cx_attr_nodiscard CxTreeIterator cx_tree_iterator( void *root, bool visit_on_exit, @@ -461,6 +479,7 @@ * @return the new tree visitor * @see cxTreeVisitorDispose() */ +cx_attr_nodiscard CxTreeVisitor cx_tree_visitor( void *root, ptrdiff_t loc_children, @@ -472,11 +491,12 @@ * The first argument points to the data the node shall contain and * the second argument may be used for additional data (e.g. an allocator). * Functions of this type shall either return a new pointer to a newly - * created node or \c NULL when allocation fails. + * created node or @c NULL when allocation fails. * - * \note the function may leave the node pointers in the struct uninitialized. + * @note the function may leave the node pointers in the struct uninitialized. * The caller is responsible to set them according to the intended use case. */ +cx_attr_nonnull_arg(1) typedef void *(*cx_tree_node_create_func)(const void *, void *); /** @@ -493,11 +513,11 @@ * Once an element cannot be added to the tree, this function returns, leaving * the iterator in a valid state pointing to the element that could not be * added. - * Also, the pointer of the created node will be stored to \p failed. + * Also, the pointer of the created node will be stored to @p failed. * The integer returned by this function denotes the number of elements obtained - * from the \p iter that have been successfully processed. - * When all elements could be processed, a \c NULL pointer will be written to - * \p failed. + * from the @p iter that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. * * The advantage of this function compared to multiple invocations of * #cx_tree_add() is that the search for the insert locations is not always @@ -520,12 +540,13 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @return the number of nodes created and added * @see cx_tree_add() */ -__attribute__((__nonnull__(1, 3, 4, 6, 7))) +cx_attr_nonnull_arg(1, 3, 4, 6, 7) +cx_attr_access_w(6) size_t cx_tree_add_iter( struct cx_iterator_base_s *iter, size_t num, @@ -545,11 +566,11 @@ * Adds multiple elements efficiently to a tree. * * Once an element cannot be added to the tree, this function returns, storing - * the pointer of the created node to \p failed. + * the pointer of the created node to @p failed. * The integer returned by this function denotes the number of elements from - * the \p src array that have been successfully processed. - * When all elements could be processed, a \c NULL pointer will be written to - * \p failed. + * the @p src array that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. * * The advantage of this function compared to multiple invocations of * #cx_tree_add() is that the search for the insert locations is not always @@ -562,8 +583,8 @@ * Refer to the documentation of #cx_tree_add() for more details. * * @param src a pointer to the source data array - * @param num the number of elements in the \p src array - * @param elem_size the size of each element in the \p src array + * @param num the number of elements in the @p src array + * @param elem_size the size of each element in the @p src array * @param sfunc a search function * @param cfunc a node creation function * @param cdata optional additional data @@ -573,12 +594,13 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @return the number of array elements successfully processed * @see cx_tree_add() */ -__attribute__((__nonnull__(1, 4, 5, 7, 8))) +cx_attr_nonnull_arg(1, 4, 5, 7, 8) +cx_attr_access_w(7) size_t cx_tree_add_array( const void *src, size_t num, @@ -599,28 +621,28 @@ * Adds data to a tree. * * An adequate location where to add the new tree node is searched with the - * specified \p sfunc. + * specified @p sfunc. * - * When a location is found, the \p cfunc will be invoked with \p cdata. + * When a location is found, the @p cfunc will be invoked with @p cdata. * - * The node returned by \p cfunc will be linked into the tree. - * When \p sfunc returned a positive integer, the new node will be linked as a + * The node returned by @p cfunc will be linked into the tree. + * When @p sfunc returned a positive integer, the new node will be linked as a * child. The other children (now siblings of the new node) are then checked - * with \p sfunc, whether they could be children of the new node and re-linked + * with @p sfunc, whether they could be children of the new node and re-linked * accordingly. * - * When \p sfunc returned zero and the found node has a parent, the new + * When @p sfunc returned zero and the found node has a parent, the new * node will be added as sibling - otherwise, the new node will be added * as a child. * - * When \p sfunc returned a negative value, the new node will not be added to + * When @p sfunc returned a negative value, the new node will not be added to * the tree and this function returns a non-zero value. - * The caller should check if \p cnode contains a node pointer and deal with the + * The caller should check if @p cnode contains a node pointer and deal with the * node that could not be added. * - * This function also returns a non-zero value when \p cfunc tries to allocate - * a new node but fails to do so. In that case, the pointer stored to \p cnode - * will be \c NULL. + * This function also returns a non-zero value when @p cfunc tries to allocate + * a new node but fails to do so. In that case, the pointer stored to @p cnode + * will be @c NULL. * * Multiple elements can be added more efficiently with * #cx_tree_add_array() or #cx_tree_add_iter(). @@ -635,12 +657,13 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @return zero when a new node was created and added to the tree, * non-zero otherwise */ -__attribute__((__nonnull__(1, 2, 3, 5, 6))) +cx_attr_nonnull_arg(1, 2, 3, 5, 6) +cx_attr_access_w(5) int cx_tree_add( const void *src, cx_tree_search_func sfunc, @@ -704,7 +727,7 @@ /** * A pointer to the root node. * - * Will be \c NULL when \c size is 0. + * Will be @c NULL when @c size is 0. */ void *root; @@ -778,6 +801,10 @@ /** * Macro to roll out the #cx_tree_node_base_s structure with a custom * node type. + * + * Must be used as first member in your custom tree struct. + * + * @param type the data type for the nodes */ #define CX_TREE_NODE_BASE(type) \ type *parent; \ @@ -788,6 +815,11 @@ /** * Macro for specifying the layout of a base node tree. + * + * When your tree uses #CX_TREE_NODE_BASE, you can use this + * macro in all tree functions that expect the layout parameters + * @c loc_parent, @c loc_children, @c loc_last_child, @c loc_prev, + * and @c loc_next. */ #define cx_tree_node_base_layout \ offsetof(struct cx_tree_node_base_s, parent),\ @@ -797,31 +829,13 @@ offsetof(struct cx_tree_node_base_s, next) /** - * Macro for obtaining the node pointer layout for a specific tree. - */ -#define cx_tree_node_layout(tree) \ - (tree)->loc_parent,\ - (tree)->loc_children,\ - (tree)->loc_last_child,\ - (tree)->loc_prev, \ - (tree)->loc_next - -/** * The class definition for arbitrary trees. */ struct cx_tree_class_s { /** - * Destructor function. - * - * Implementations SHALL invoke the node destructor functions if provided - * and SHALL deallocate the tree memory. - */ - void (*destructor)(struct cx_tree_s *); - - /** * Member function for inserting a single element. * - * Implementations SHALL NOT simply invoke \p insert_many as this comes + * Implementations SHALL NOT simply invoke @p insert_many as this comes * with too much overhead. */ int (*insert_element)( @@ -847,21 +861,9 @@ void *(*find)( struct cx_tree_s *tree, const void *subtree, - const void *data + const void *data, + size_t depth ); - - /** - * Member function for creating an iterator for the tree. - */ - CxTreeIterator (*iterator)( - struct cx_tree_s *tree, - bool visit_on_exit - ); - - /** - * Member function for creating a visitor for the tree. - */ - CxTreeVisitor (*visitor)(struct cx_tree_s *tree); }; /** @@ -869,16 +871,80 @@ */ typedef struct cx_tree_s CxTree; + +/** + * Destroys a node and it's subtree. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * When this function is invoked on the root node of the tree, it destroys the + * tree contents, but - in contrast to #cxTreeFree() - not the tree + * structure, leaving an empty tree behind. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @attention This function will not free the memory of the nodes with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to remove + * @see cxTreeFree() + */ +cx_attr_nonnull +void cxTreeDestroySubtree(CxTree *tree, void *node); + + +/** + * Destroys the tree contents. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * This is a convenience macro for invoking #cxTreeDestroySubtree() on the + * root node of the tree. + * + * @attention Be careful when calling this function when no destructor function + * is registered that actually frees the memory of nodes. In that case you will + * need a reference to the (former) root node of the tree somewhere or + * otherwise you will be leaking memory. + * + * @param tree the tree + * @see cxTreeDestroySubtree() + */ +#define cxTreeClear(tree) cxTreeDestroySubtree(tree, tree->root) + +/** + * Deallocates the tree structure. + * + * The destructor functions are invoked for each node, starting with the leaf + * nodes. + * It is guaranteed that for each node the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will only invoke the destructor functions + * on the nodes. + * It will NOT additionally free the nodes with the tree's allocator, because + * that would cause a double-free in most scenarios where the advanced + * destructor is already freeing the memory. + * + * @param tree the tree to free + */ +void cxTreeFree(CxTree *tree); + /** * Creates a new tree structure based on the specified layout. * - * The specified \p allocator will be used for creating the tree struct - * and SHALL be used by \p create_func to allocate memory for the nodes. + * The specified @p allocator will be used for creating the tree struct + * and SHALL be used by @p create_func to allocate memory for the nodes. * - * \note This function will also register an advanced destructor which + * @note This function will also register an advanced destructor which * will free the nodes with the allocator's free() method. * * @param allocator the allocator that shall be used + * (if @c NULL, a default stdlib allocator will be used) * @param create_func a function that creates new nodes * @param search_func a function that compares two nodes * @param search_data_func a function that compares a node with data @@ -886,13 +952,16 @@ * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @return the new tree * @see cxTreeCreateSimple() * @see cxTreeCreateWrapped() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull_arg(2, 3, 4) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) CxTree *cxTreeCreate( const CxAllocator *allocator, cx_tree_node_create_func create_func, @@ -908,57 +977,51 @@ /** * Creates a new tree structure based on a default layout. * - * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first * member (or at least respect the default offsets specified in the tree * struct) and they MUST be allocated with the specified allocator. * - * \note This function will also register an advanced destructor which + * @note This function will also register an advanced destructor which * will free the nodes with the allocator's free() method. * - * @param allocator the allocator that shall be used - * @param create_func a function that creates new nodes - * @param search_func a function that compares two nodes - * @param search_data_func a function that compares a node with data - * @return the new tree + * @param allocator (@c CxAllocator*) the allocator that shall be used + * @param create_func (@c cx_tree_node_create_func) a function that creates new nodes + * @param search_func (@c cx_tree_search_func) a function that compares two nodes + * @param search_data_func (@c cx_tree_search_data_func) a function that compares a node with data + * @return (@c CxTree*) the new tree * @see cxTreeCreate() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTree *cxTreeCreateSimple( - const CxAllocator *allocator, - cx_tree_node_create_func create_func, - cx_tree_search_func search_func, - cx_tree_search_data_func search_data_func -) { - return cxTreeCreate( - allocator, - create_func, - search_func, - search_data_func, - cx_tree_node_base_layout - ); -} +#define cxTreeCreateSimple(\ + allocator, create_func, search_func, search_data_func \ +) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ +cx_tree_node_base_layout) /** * Creates a new tree structure based on an existing tree. * - * The specified \p allocator will be used for creating the tree struct. + * The specified @p allocator will be used for creating the tree struct. * - * \attention This function will create an incompletely defined tree structure + * @attention This function will create an incompletely defined tree structure * where neither the create function, the search function, nor a destructor * will be set. If you wish to use any of this functionality for the wrapped * tree, you need to specify those functions afterwards. * + * @param allocator the allocator that was used for nodes of the wrapped tree + * (if @c NULL, a default stdlib allocator is assumed) * @param root the root node of the tree that shall be wrapped * @param loc_parent offset in the node struct for the parent pointer * @param loc_children offset in the node struct for the children linked list * @param loc_last_child optional offset in the node struct for the pointer to * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev offset in the node struct for the prev pointer + * @param loc_prev optional offset in the node struct for the prev pointer * @param loc_next offset in the node struct for the next pointer * @return the new tree * @see cxTreeCreate() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull_arg(2) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) CxTree *cxTreeCreateWrapped( const CxAllocator *allocator, void *root, @@ -970,32 +1033,18 @@ ); /** - * Destroys the tree structure. - * - * \attention This function will only invoke the destructor functions - * on the nodes, if specified. - * It will NOT additionally free the nodes with the tree's allocator, because - * that would cause a double-free in most scenarios. - * - * @param tree the tree to destroy - */ -__attribute__((__nonnull__)) -static inline void cxTreeDestroy(CxTree *tree) { - tree->cl->destructor(tree); -} - -/** * Inserts data into the tree. * - * \remark For this function to work, the tree needs specified search and + * @remark For this function to work, the tree needs specified search and * create functions, which might not be available for wrapped trees * (see #cxTreeCreateWrapped()). * * @param tree the tree * @param data the data to insert - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxTreeInsert( CxTree *tree, const void *data @@ -1006,7 +1055,7 @@ /** * Inserts elements provided by an iterator efficiently into the tree. * - * \remark For this function to work, the tree needs specified search and + * @remark For this function to work, the tree needs specified search and * create functions, which might not be available for wrapped trees * (see #cxTreeCreateWrapped()). * @@ -1015,7 +1064,7 @@ * @param n the maximum number of elements to insert * @return the number of elements that could be successfully inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxTreeInsertIter( CxTree *tree, struct cx_iterator_base_s *iter, @@ -1027,7 +1076,7 @@ /** * Inserts an array of data efficiently into the tree. * - * \remark For this function to work, the tree needs specified search and + * @remark For this function to work, the tree needs specified search and * create functions, which might not be available for wrapped trees * (see #cxTreeCreateWrapped()). * @@ -1037,7 +1086,7 @@ * @param n the number of elements in the array * @return the number of elements that could be successfully inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxTreeInsertArray( CxTree *tree, const void *data, @@ -1053,44 +1102,51 @@ /** * Searches the data in the specified tree. * - * \remark For this function to work, the tree needs a specified \c search_data + * @remark For this function to work, the tree needs a specified @c search_data * function, which might not be available wrapped trees * (see #cxTreeCreateWrapped()). * * @param tree the tree * @param data the data to search for - * @return the first matching node, or \c NULL when the data cannot be found + * @return the first matching node, or @c NULL when the data cannot be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxTreeFind( CxTree *tree, const void *data ) { - return tree->cl->find(tree, tree->root, data); + return tree->cl->find(tree, tree->root, data, 0); } /** * Searches the data in the specified subtree. * - * \note When \p subtree_root is not part of the \p tree, the behavior is + * When @p max_depth is zero, the depth is not limited. + * The @p subtree_root itself is on depth 1 and its children have depth 2. + * + * @note When @p subtree_root is not part of the @p tree, the behavior is * undefined. * - * \remark For this function to work, the tree needs a specified \c search_data + * @remark For this function to work, the tree needs a specified @c search_data * function, which might not be the case for wrapped trees * (see #cxTreeCreateWrapped()). * * @param tree the tree * @param data the data to search for * @param subtree_root the node where to start - * @return the first matching node, or \c NULL when the data cannot be found + * @param max_depth the maximum search depth + * @return the first matching node, or @c NULL when the data cannot be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxTreeFindInSubtree( CxTree *tree, const void *data, - void *subtree_root + void *subtree_root, + size_t max_depth ) { - return tree->cl->find(tree, subtree_root, data); + return tree->cl->find(tree, subtree_root, data, max_depth); } /** @@ -1100,7 +1156,8 @@ * @param subtree_root the root node of the subtree * @return the number of nodes in the specified subtree */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); /** @@ -1108,9 +1165,10 @@ * * @param tree the tree * @param subtree_root the root node of the subtree - * @return the tree depth including the \p subtree_root + * @return the tree depth including the @p subtree_root */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** @@ -1119,24 +1177,69 @@ * @param tree the tree * @return the tree depth, counting the root as one */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard size_t cxTreeDepth(CxTree *tree); /** + * Creates a depth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterateSubtree( + CxTree *tree, + void *node, + bool visit_on_exit +) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a breadth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +/** * Creates a depth-first iterator for the specified tree. * * @param tree the tree to iterate * @param visit_on_exit true, if the iterator shall visit a node again when - * leaving the sub-tree + * leaving the subtree * @return a tree iterator (depth-first) - * @see cxTreeVisitor() + * @see cxTreeVisit() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeIterator cxTreeIterator( +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterate( CxTree *tree, bool visit_on_exit ) { - return tree->cl->iterator(tree, visit_on_exit); + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); } /** @@ -1144,32 +1247,53 @@ * * @param tree the tree to iterate * @return a tree visitor (a.k.a. breadth-first iterator) - * @see cxTreeIterator() + * @see cxTreeIterate() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) { - return tree->cl->visitor(tree); +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); } /** + * Sets the (new) parent of the specified child. + * + * If the @p child is not already member of the tree, this function behaves + * as #cxTreeAddChildNode(). + * + * @param tree the tree + * @param parent the (new) parent of the child + * @param child the node to add + * @see cxTreeAddChildNode() + */ +cx_attr_nonnull +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +); + +/** * Adds a new node to the tree. * - * \attention The node may be externally created, but MUST obey the same rules + * If the @p child is already member of the tree, the behavior is undefined. + * Use #cxTreeSetParent() if you want to move a subtree to another location. + * + * @attention The node may be externally created, but MUST obey the same rules * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use * the same allocator). * * @param tree the tree * @param parent the parent of the node to add * @param child the node to add + * @see cxTreeSetParent() */ -__attribute__((__nonnull__)) -static inline void cxTreeAddChildNode( +cx_attr_nonnull +void cxTreeAddChildNode( CxTree *tree, void *parent, - void *child) { - cx_tree_link(parent, child, cx_tree_node_layout(tree)); - tree->size++; -} + void *child +); /** * Creates a new node and adds it to the tree. @@ -1188,7 +1312,7 @@ * @return zero when the new node was created, non-zero on allocation failure * @see cxTreeInsert() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxTreeAddChild( CxTree *tree, void *parent, @@ -1199,13 +1323,15 @@ * A function that is invoked when a node needs to be re-linked to a new parent. * * When a node is re-linked, sometimes the contents need to be updated. - * This callback is invoked by #cxTreeRemove() so that those updates can be - * applied when re-linking the children of the removed node. + * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode() + * so that those updates can be applied when re-linking the children of the + * removed node. * * @param node the affected node * @param old_parent the old parent of the node * @param new_parent the new parent of the node */ +cx_attr_nonnull typedef void (*cx_tree_relink_func)( void *node, const void *old_parent, @@ -1217,17 +1343,17 @@ * * If the node is not part of the tree, the behavior is undefined. * - * \note The destructor function, if any, will \em not be invoked. That means + * @note The destructor function, if any, will @em not be invoked. That means * you will need to free the removed node by yourself, eventually. * * @param tree the tree * @param node the node to remove (must not be the root node) * @param relink_func optional callback to update the content of each re-linked * node - * @return zero on success, non-zero if \p node is the root node of the tree + * @return zero on success, non-zero if @p node is the root node of the tree */ -__attribute__((__nonnull__(1,2))) -int cxTreeRemove( +cx_attr_nonnull_arg(1, 2) +int cxTreeRemoveNode( CxTree *tree, void *node, cx_tree_relink_func relink_func @@ -1238,15 +1364,40 @@ * * If the node is not part of the tree, the behavior is undefined. * - * \note The destructor function, if any, will \em not be invoked. That means + * @note The destructor function, if any, will @em not be invoked. That means * you will need to free the removed subtree by yourself, eventually. * * @param tree the tree * @param node the node to remove */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxTreeRemoveSubtree(CxTree *tree, void *node); +/** + * Destroys a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will not free the memory of the node with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to destroy (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if @p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + #ifdef __cplusplus } // extern "C" #endif diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/hash_key.c --- a/ucx/hash_key.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/hash_key.c Mon Jan 06 22:22:55 2025 +0100 @@ -40,7 +40,7 @@ unsigned m = 0x5bd1e995; unsigned r = 24; - unsigned h = 25 ^ len; + unsigned h = 25 ^ (unsigned) len; unsigned i = 0; while (len >= 4) { unsigned k = data[i + 0] & 0xFF; diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/hash_map.c --- a/ucx/hash_map.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/hash_map.c Mon Jan 06 22:22:55 2025 +0100 @@ -27,10 +27,10 @@ */ #include "cx/hash_map.h" -#include "cx/utils.h" #include #include +#include struct cx_hash_map_element_s { /** A pointer to the next element in the current bucket. */ @@ -45,7 +45,7 @@ static void cx_hash_map_clear(struct cx_map_s *map) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - cx_for_n(i, hash_map->bucket_count) { + for (size_t i = 0; i < hash_map->bucket_count; i++) { struct cx_hash_map_element_s *elem = hash_map->buckets[i]; if (elem != NULL) { do { @@ -115,9 +115,7 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) { - return -1; - } + if (e == NULL) return -1; // write the value if (map->collection.store_pointer) { @@ -128,9 +126,7 @@ // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) { - return -1; - } + if (kd == NULL) return -1; memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -173,17 +169,28 @@ /** * Helper function to avoid code duplication. * + * If @p remove is true, and @p targetbuf is @c NULL, the element + * will be destroyed when found. + * + * If @p remove is true, and @p targetbuf is set, the element will + * be copied to that buffer and no destructor function is called. + * + * If @p remove is false, @p targetbuf must not be non-null and + * either the pointer, when the map is storing pointers, is copied + * to the target buffer, or a pointer to the stored object will + * be copied to the target buffer. + * * @param map the map * @param key the key to look up + * @param targetbuf see description * @param remove flag indicating whether the looked up entry shall be removed - * @param destroy flag indicating whether the destructor shall be invoked - * @return a pointer to the value corresponding to the key or \c NULL + * @return zero, if the key was found, non-zero otherwise */ -static void *cx_hash_map_get_remove( +static int cx_hash_map_get_remove( CxMap *map, CxHashKey key, - bool remove, - bool destroy + void *targetbuf, + bool remove ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; @@ -199,27 +206,31 @@ while (elm && elm->key.hash <= hash) { if (elm->key.hash == hash && elm->key.len == key.len) { if (memcmp(elm->key.data, key.data, key.len) == 0) { - void *data = NULL; - if (destroy) { - cx_invoke_destructor(map, elm->data); + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); + } else { + memcpy(targetbuf, elm->data, map->collection.elem_size); + } + cx_hash_map_unlink(hash_map, slot, prev, elm); } else { + assert(targetbuf != NULL); + void *data = NULL; if (map->collection.store_pointer) { data = *(void **) elm->data; } else { data = elm->data; } + memcpy(targetbuf, &data, sizeof(void *)); } - if (remove) { - cx_hash_map_unlink(hash_map, slot, prev, elm); - } - return data; + return 0; } } prev = elm; elm = prev->next; } - return NULL; + return 1; } static void *cx_hash_map_get( @@ -227,15 +238,17 @@ CxHashKey key ) { // we can safely cast, because we know the map stays untouched - return cx_hash_map_get_remove((CxMap *) map, key, false, false); + void *ptr = NULL; + int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false); + return found == 0 ? ptr : NULL; } -static void *cx_hash_map_remove( +static int cx_hash_map_remove( CxMap *map, CxHashKey key, - bool destroy + void *targetbuf ) { - return cx_hash_map_get_remove(map, key, true, destroy); + return cx_hash_map_get_remove(map, key, targetbuf, true); } static void *cx_hash_map_iter_current_entry(const void *it) { @@ -346,7 +359,7 @@ iter.base.current = cx_hash_map_iter_current_value; break; default: - assert(false); + assert(false); // LCOV_EXCL_LINE } iter.base.valid = cx_hash_map_iter_valid; @@ -393,6 +406,10 @@ size_t itemsize, size_t buckets ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (buckets == 0) { // implementation defined default buckets = 16; @@ -406,10 +423,10 @@ map->bucket_count = buckets; map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *)); - if (map->buckets == NULL) { + if (map->buckets == NULL) { // LCOV_EXCL_START cxFree(allocator, map); return NULL; - } + } // LCOV_EXCL_STOP // initialize base members map->base.cl = &cx_hash_map_class; @@ -431,17 +448,19 @@ if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) { size_t new_bucket_count = (map->collection.size * 5) >> 1; + if (new_bucket_count < hash_map->bucket_count) { + errno = EOVERFLOW; + return 1; + } struct cx_hash_map_element_s **new_buckets = cxCalloc( map->collection.allocator, new_bucket_count, sizeof(struct cx_hash_map_element_s *) ); - if (new_buckets == NULL) { - return 1; - } + if (new_buckets == NULL) return 1; // iterate through the elements and assign them to their new slots - cx_for_n(slot, hash_map->bucket_count) { + for (size_t slot = 0; slot < hash_map->bucket_count; slot++) { struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; while (elm != NULL) { struct cx_hash_map_element_s *next = elm->next; diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/iterator.c --- a/ucx/iterator.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/iterator.c Mon Jan 06 22:22:55 2025 +0100 @@ -40,6 +40,11 @@ return iter->elem_handle; } +static void *cx_iter_current_ptr(const void *it) { + const struct cx_iterator_s *iter = it; + return *(void**)iter->elem_handle; +} + static void cx_iter_next_fast(void *it) { struct cx_iterator_s *iter = it; if (iter->base.remove) { @@ -110,3 +115,22 @@ iter.base.mutating = false; return iter; } + +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +) { + CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order); + iter.base.current = cx_iter_current_ptr; + return iter; +} + +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +) { + CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false); + iter.base.mutating = false; + return iter; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/json.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/json.c Mon Jan 06 22:22:55 2025 +0100 @@ -0,0 +1,1212 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/json.h" +#include "cx/compare.h" + +#include +#include +#include +#include +#include +#include + +/* + * RFC 8259 + * https://tools.ietf.org/html/rfc8259 + */ + +static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; + +static int json_cmp_objvalue(const void *l, const void *r) { + const CxJsonObjValue *left = l; + const CxJsonObjValue *right = r; + return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); +} + +static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { + assert(obj->type == CX_JSON_OBJECT); + CxJsonObjValue kv_dummy; + kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); + size_t index = cx_array_binary_search( + obj->value.object.values, + obj->value.object.values_size, + sizeof(CxJsonObjValue), + &kv_dummy, + json_cmp_objvalue + ); + if (index == obj->value.object.values_size) { + return NULL; + } else { + return &obj->value.object.values[index]; + } +} + +static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { + assert(objv->type == CX_JSON_OBJECT); + const CxAllocator * const al = objv->allocator; + CxJsonObject *obj = &(objv->value.object); + + // determine the index where we need to insert the new member + size_t index = cx_array_binary_search_sup( + obj->values, + obj->values_size, + sizeof(CxJsonObjValue), + &member, json_cmp_objvalue + ); + + // is the name already present? + if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { + // free the original value + cx_strfree_a(al, &obj->values[index].name); + cxJsonValueFree(obj->values[index].value); + // replace the item + obj->values[index] = member; + + // nothing more to do + return 0; + } + + // determine the old capacity and reserve for one more element + CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); + size_t oldcap = obj->values_capacity; + if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; + + // check the new capacity, if we need to realloc the index array + size_t newcap = obj->values_capacity; + if (newcap > oldcap) { + if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { + return 1; + } + } + + // check if append or insert + if (index < obj->values_size) { + // move the other elements + memmove( + &obj->values[index+1], + &obj->values[index], + (obj->values_size - index) * sizeof(CxJsonObjValue) + ); + // increase indices for the moved elements + for (size_t i = 0; i < obj->values_size ; i++) { + if (obj->indices[i] >= index) { + obj->indices[i]++; + } + } + } + + // insert the element and set the index + obj->values[index] = member; + obj->indices[obj->values_size] = index; + obj->values_size++; + + return 0; +} + +static void token_destroy(CxJsonToken *token) { + if (token->allocated) { + cx_strfree(&token->content); + } +} + +static int num_isexp(const char *content, size_t length, size_t pos) { + if (pos >= length) { + return 0; + } + + int ok = 0; + for (size_t i = pos; i < length; i++) { + char c = content[i]; + if (isdigit(c)) { + ok = 1; + } else if (i == pos) { + if (!(c == '+' || c == '-')) { + return 0; + } + } else { + return 0; + } + } + + return ok; +} + +static CxJsonTokenType token_numbertype(const char *content, size_t length) { + if (length == 0) return CX_JSON_TOKEN_ERROR; + + if (content[0] != '-' && !isdigit(content[0])) { + return CX_JSON_TOKEN_ERROR; + } + + CxJsonTokenType type = CX_JSON_TOKEN_INTEGER; + for (size_t i = 1; i < length; i++) { + if (content[i] == '.') { + if (type == CX_JSON_TOKEN_NUMBER) { + return CX_JSON_TOKEN_ERROR; // more than one decimal separator + } + type = CX_JSON_TOKEN_NUMBER; + } else if (content[i] == 'e' || content[i] == 'E') { + return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; + } else if (!isdigit(content[i])) { + return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep + } + } + + return type; +} + +static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { + cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); + bool allocated = false; + if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { + allocated = true; + str = cx_strcat_m(json->uncompleted.content, 1, str); + if (str.ptr == NULL) { // LCOV_EXCL_START + return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; + } // LCOV_EXCL_STOP + } + json->uncompleted = (CxJsonToken){0}; + CxJsonTokenType ttype; + if (isstring) { + ttype = CX_JSON_TOKEN_STRING; + } else { + cxstring s = cx_strcast(str); + if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) + || !cx_strcmp(s, CX_STR("null"))) { + ttype = CX_JSON_TOKEN_LITERAL; + } else { + ttype = token_numbertype(str.ptr, str.length); + } + } + if (ttype == CX_JSON_TOKEN_ERROR) { + if (allocated) { + cx_strfree(&str); + } + return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; + } + return (CxJsonToken){ttype, allocated, str}; +} + +static CxJsonTokenType char2ttype(char c) { + switch (c) { + case '[': { + return CX_JSON_TOKEN_BEGIN_ARRAY; + } + case '{': { + return CX_JSON_TOKEN_BEGIN_OBJECT; + } + case ']': { + return CX_JSON_TOKEN_END_ARRAY; + } + case '}': { + return CX_JSON_TOKEN_END_OBJECT; + } + case ':': { + return CX_JSON_TOKEN_NAME_SEPARATOR; + } + case ',': { + return CX_JSON_TOKEN_VALUE_SEPARATOR; + } + case '"': { + return CX_JSON_TOKEN_STRING; + } + default: { + if (isspace(c)) { + return CX_JSON_TOKEN_SPACE; + } + } + } + return CX_JSON_NO_TOKEN; +} + +static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { + // check if there is data in the buffer + if (cxBufferEof(&json->buffer)) { + return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? + CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; + } + + // current token type and start index + CxJsonTokenType ttype = json->uncompleted.tokentype; + size_t token_start = json->buffer.pos; + + for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { + char c = json->buffer.space[i]; + if (ttype != CX_JSON_TOKEN_STRING) { + // currently non-string token + CxJsonTokenType ctype = char2ttype(c); // start of new token? + if (ttype == CX_JSON_NO_TOKEN) { + if (ctype == CX_JSON_TOKEN_SPACE) { + json->buffer.pos++; + continue; + } else if (ctype == CX_JSON_TOKEN_STRING) { + // begin string + ttype = CX_JSON_TOKEN_STRING; + token_start = i; + } else if (ctype != CX_JSON_NO_TOKEN) { + // single-char token + json->buffer.pos = i + 1; + *result = (CxJsonToken){ctype, false, {NULL, 0}}; + return CX_JSON_NO_ERROR; + } else { + ttype = CX_JSON_TOKEN_LITERAL; // number or literal + token_start = i; + } + } else { + // finish token + if (ctype != CX_JSON_NO_TOKEN) { + *result = token_create(json, false, token_start, i); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + if (result->tokentype == CX_JSON_TOKEN_ERROR) { + return CX_JSON_FORMAT_ERROR_NUMBER; + } + json->buffer.pos = i; + return CX_JSON_NO_ERROR; + } + } + } else { + // currently inside a string + if (json->tokenizer_escape) { + json->tokenizer_escape = false; + } else { + if (c == '"') { + *result = token_create(json, true, token_start, i + 1); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->buffer.pos = i + 1; + return CX_JSON_NO_ERROR; + } else if (c == '\\') { + json->tokenizer_escape = true; + } + } + } + } + + if (ttype != CX_JSON_NO_TOKEN) { + // uncompleted token + size_t uncompleted_len = json->buffer.size - token_start; + if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { + // current token is uncompleted + // save current token content + CxJsonToken uncompleted = { + ttype, true, + cx_strdup(cx_strn(json->buffer.space + token_start, uncompleted_len)) + }; + if (uncompleted.content.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted = uncompleted; + } else { + // previously we also had an uncompleted token + // combine the uncompleted token with the current token + assert(json->uncompleted.allocated); + cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, + cx_strn(json->buffer.space + token_start, uncompleted_len)); + if (str.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted.content = str; + } + // advance the buffer position - we saved the stuff in the uncompleted token + json->buffer.pos += uncompleted_len; + } + + return CX_JSON_INCOMPLETE_DATA; +} + +static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { + // TODO: support more escape sequences + // we know that the unescaped string will be shorter by at least 2 chars + cxmutstr result; + result.length = 0; + result.ptr = cxMalloc(a, str.length - 1); + if (result.ptr == NULL) return result; // LCOV_EXCL_LINE + + bool u = false; + for (size_t i = 1; i < str.length - 1; i++) { + char c = str.ptr[i]; + if (u) { + u = false; + if (c == 'n') { + c = '\n'; + } else if (c == 't') { + c = '\t'; + } + result.ptr[result.length++] = c; + } else { + if (c == '\\') { + u = true; + } else { + result.ptr[result.length++] = c; + } + } + } + result.ptr[result.length] = 0; + + return result; +} + +static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) { + CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); + if (v == NULL) return NULL; // LCOV_EXCL_LINE + + // initialize the value + v->type = type; + v->allocator = json->allocator; + if (type == CX_JSON_ARRAY) { + cx_array_initialize_a(json->allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE + } else if (type == CX_JSON_OBJECT) { + cx_array_initialize_a(json->allocator, v->value.object.values, 16); + v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); + if (v->value.object.values == NULL || + v->value.object.indices == NULL) + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + + // add the new value to a possible parent + if (json->vbuf_size > 0) { + CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; + assert(parent != NULL); + if (parent->type == CX_JSON_ARRAY) { + CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); + if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } else if (parent->type == CX_JSON_OBJECT) { + // the member was already created after parsing the name + assert(json->uncompleted_member.name.ptr != NULL); + json->uncompleted_member.value = v; + if (json_add_objvalue(parent, json->uncompleted_member)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + } else { + assert(false); // LCOV_EXCL_LINE + } + } + + // add the new value to the stack, if it is an array or object + if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { + CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); + if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } + + // if currently no value is parsed, this is now the value of interest + if (json->parsed == NULL) { + json->parsed = v; + } + + return v; + // LCOV_EXCL_START +create_json_value_exit_error: + cxJsonValueFree(v); + return NULL; + // LCOV_EXCL_STOP +} + +#define JP_STATE_VALUE_BEGIN 0 +#define JP_STATE_VALUE_END 10 +#define JP_STATE_VALUE_BEGIN_OBJ 1 +#define JP_STATE_OBJ_SEP_OR_CLOSE 11 +#define JP_STATE_VALUE_BEGIN_AR 2 +#define JP_STATE_ARRAY_SEP_OR_CLOSE 12 +#define JP_STATE_OBJ_NAME_OR_CLOSE 5 +#define JP_STATE_OBJ_NAME 6 +#define JP_STATE_OBJ_COLON 7 + +void cxJsonInit(CxJson *json, const CxAllocator *allocator) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + memset(json, 0, sizeof(CxJson)); + json->allocator = allocator; + + json->states = json->states_internal; + json->states_capacity = cx_nmemb(json->states_internal); + json->states[0] = JP_STATE_VALUE_BEGIN; + json->states_size = 1; + + json->vbuf = json->vbuf_internal; + json->vbuf_capacity = cx_nmemb(json->vbuf_internal); +} + +void cxJsonDestroy(CxJson *json) { + cxBufferDestroy(&json->buffer); + if (json->states != json->states_internal) { + free(json->states); + } + if (json->vbuf != json->vbuf_internal) { + free(json->vbuf); + } + cxJsonValueFree(json->parsed); + json->parsed = NULL; + if (json->uncompleted_member.name.ptr != NULL) { + cx_strfree_a(json->allocator, &json->uncompleted_member.name); + json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; + } +} + +int cxJsonFilln(CxJson *json, const char *buf, size_t size) { + if (cxBufferEof(&json->buffer)) { + // reinitialize the buffer + cxBufferDestroy(&json->buffer); + cxBufferInit(&json->buffer, (char*) buf, size, + NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); + json->buffer.size = size; + return 0; + } else { + return size != cxBufferAppend(buf, 1, size, &json->buffer); + } +} + +static void json_add_state(CxJson *json, int state) { + // we have guaranteed the necessary space with cx_array_simple_reserve() + // therefore, we can safely add the state in the simplest way possible + json->states[json->states_size++] = state; +} + +#define return_rec(code) \ + token_destroy(&token); \ + return code + +static enum cx_json_status json_parse(CxJson *json) { + // Reserve a pointer for a possibly read value + CxJsonValue *vbuf = NULL; + + // grab the next token + CxJsonToken token; + { + enum cx_json_status ret = token_parse_next(json, &token); + if (ret != CX_JSON_NO_ERROR) { + return ret; + } + } + + // pop the current state + assert(json->states_size > 0); + int state = json->states[--json->states_size]; + + // guarantee that at least two more states fit on the stack + CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); + if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + + + // 0 JP_STATE_VALUE_BEGIN value begin + // 10 JP_STATE_VALUE_END expect value end + // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object) + // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose + // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array) + // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose + // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose + // 6 JP_STATE_OBJ_NAME object, expect name + // 7 JP_STATE_OBJ_COLON object, expect ':' + + if (state < 3) { + // push expected end state to the stack + json_add_state(json, 10 + state); + switch (token.tokentype) { + case CX_JSON_TOKEN_BEGIN_ARRAY: { + if (create_json_value(json, CX_JSON_ARRAY) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_BEGIN_OBJECT: { + if (create_json_value(json, CX_JSON_OBJECT) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_STRING: { + if ((vbuf = create_json_value(json, CX_JSON_STRING)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + cxmutstr str = unescape_string(json->allocator, token.content); + if (str.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + vbuf->value.string = str; + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_INTEGER: + case CX_JSON_TOKEN_NUMBER: { + int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; + if (NULL == (vbuf = create_json_value(json, type))) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (type == CX_JSON_INTEGER) { + if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } else { + if (cx_strtod(token.content, &vbuf->value.number)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_LITERAL: { + if ((vbuf = create_json_value(json, CX_JSON_LITERAL)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { + vbuf->value.literal = CX_JSON_TRUE; + } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { + vbuf->value.literal = CX_JSON_FALSE; + } else { + vbuf->value.literal = CX_JSON_NULL; + } + return_rec(CX_JSON_NO_ERROR); + } + default: { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } + } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) { + // expect ',' or ']' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { + // discard the array from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { + if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + // expect string + if (token.tokentype != CX_JSON_TOKEN_STRING) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + + // add new entry + cxmutstr name = unescape_string(json->allocator, token.content); + if (name.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + assert(json->uncompleted_member.name.ptr == NULL); + json->uncompleted_member.name = name; + assert(json->vbuf_size > 0); + + // next state + json_add_state(json, JP_STATE_OBJ_COLON); + return_rec(CX_JSON_NO_ERROR); + } + } else if (state == JP_STATE_OBJ_COLON) { + // expect ':' + if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + // next state + json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ); + return_rec(CX_JSON_NO_ERROR); + } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) { + // expect ',' or '}' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_OBJ_NAME); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else { + // should be unreachable + assert(false); + return_rec(-1); + } +} + +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { + // check if buffer has been filled + if (json->buffer.space == NULL) { + return CX_JSON_NULL_DATA; + } + + // initialize output value + *value = &cx_json_value_nothing; + + // parse data + CxJsonStatus result; + do { + result = json_parse(json); + if (result == CX_JSON_NO_ERROR && json->states_size == 1) { + // final state reached + assert(json->states[0] == JP_STATE_VALUE_END); + assert(json->vbuf_size == 0); + + // write output value + *value = json->parsed; + json->parsed = NULL; + + // re-initialize state machine + json->states[0] = JP_STATE_VALUE_BEGIN; + + return CX_JSON_NO_ERROR; + } + } while (result == CX_JSON_NO_ERROR); + + // the parser might think there is no data + // but when we did not reach the final state, + // we know that there must be more to come + if (result == CX_JSON_NO_DATA && json->states_size > 1) { + return CX_JSON_INCOMPLETE_DATA; + } + + return result; +} + +void cxJsonValueFree(CxJsonValue *value) { + if (value == NULL || value->type == CX_JSON_NOTHING) return; + switch (value->type) { + case CX_JSON_OBJECT: { + CxJsonObject obj = value->value.object; + for (size_t i = 0; i < obj.values_size; i++) { + cxJsonValueFree(obj.values[i].value); + cx_strfree_a(value->allocator, &obj.values[i].name); + } + cxFree(value->allocator, obj.values); + cxFree(value->allocator, obj.indices); + break; + } + case CX_JSON_ARRAY: { + CxJsonArray array = value->value.array; + for (size_t i = 0; i < array.array_size; i++) { + cxJsonValueFree(array.array[i]); + } + cxFree(value->allocator, array.array); + break; + } + case CX_JSON_STRING: { + cxFree(value->allocator, value->value.string.ptr); + break; + } + default: { + break; + } + } + cxFree(value->allocator, value); +} + +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_OBJECT; + cx_array_initialize_a(allocator, v->value.object.values, 16); + if (v->value.object.values == NULL) { // LCOV_EXCL_START + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); + if (v->value.object.indices == NULL) { // LCOV_EXCL_START + cxFree(allocator, v->value.object.values); + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + return v; +} + +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_ARRAY; + cx_array_initialize_a(allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + return v; +} + +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_NUMBER; + v->value.number = num; + return v; +} + +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_INTEGER; + v->value.integer = num; + return v; +} + +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { + return cxJsonCreateCxString(allocator, cx_str(str)); +} + +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_STRING; + cxmutstr s = cx_strdup_a(allocator, str); + if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } + v->value.string = s; + return v; +} + +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_LITERAL; + v->value.literal = lit; + return v; +} + +// LCOV_EXCL_START +// never called as long as malloc() does not return NULL +static void cx_json_arr_free_temp(CxJsonValue** values, size_t count) { + for (size_t i = 0; i < count; i++) { + if (values[i] == NULL) break; + cxJsonValueFree(values[i]); + } + free(values); +} +// LCOV_EXCL_STOP + +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateNumber(arr->allocator, num[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateInteger(arr->allocator, num[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateString(arr->allocator, str[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateCxString(arr->allocator, str[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { + CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); + assert(arr->type == CX_JSON_ARRAY); + return cx_array_simple_copy_a(&value_realloc, + arr->value.array.array, + arr->value.array.array_size, + val, count + ); +} + +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { + cxmutstr k = cx_strdup_a(obj->allocator, name); + if (k.ptr == NULL) return -1; + CxJsonObjValue kv = {k, child}; + if (json_add_objvalue(obj, kv)) { + cx_strfree_a(obj->allocator, &k); + return 1; + } else { + return 0; + } +} + +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateObj(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateArr(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { + CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { + CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { + CxJsonValue* v = cxJsonCreateString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { + CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { + CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} + return v; +} + +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return &cx_json_value_nothing; + } + return value->value.array.array[index]; +} + +CxIterator cxJsonArrIter(const CxJsonValue *value) { + return cxIteratorPtr( + value->value.array.array, + value->value.array.array_size + ); +} + +CxIterator cxJsonObjIter(const CxJsonValue *value) { + return cxIterator( + value->value.object.values, + sizeof(CxJsonObjValue), + value->value.object.values_size + ); +} + +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { + CxJsonObjValue *member = json_find_objvalue(value, name); + if (member == NULL) { + return &cx_json_value_nothing; + } else { + return member->value; + } +} + +static const CxJsonWriter cx_json_writer_default = { + false, + true, + 255, + false, + 4 +}; + +CxJsonWriter cxJsonWriterCompact(void) { + return cx_json_writer_default; +} + +CxJsonWriter cxJsonWriterPretty(bool use_spaces) { + return (CxJsonWriter) { + true, + true, + 255, + use_spaces, + 4 + }; +} + +static int cx_json_writer_indent( + void *target, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + if (depth == 0) return 0; + + // determine the width and characters to use + const char* indent; // for 32 prepared chars + size_t width = depth; + if (settings->indent_space) { + if (settings->indent == 0) return 0; + width *= settings->indent; + indent = " "; + } else { + indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + } + + // calculate the number of write calls and write + size_t full = width / 32; + size_t remaining = width % 32; + for (size_t i = 0; i < full; i++) { + if (32 != wfunc(indent, 1, 32, target)) return 1; + } + if (remaining != wfunc(indent, 1, remaining, target)) return 1; + + return 0; +} + + +int cx_json_write_rec( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + // keep track of written items + // the idea is to reduce the number of jumps for error checking + size_t actual = 0, expected = 0; + + // small buffer for number to string conversions + char numbuf[32]; + + // recursively write the values + switch (value->type) { + case CX_JSON_OBJECT: { + const char *begin_obj = "{\n"; + if (settings->pretty) { + actual += wfunc(begin_obj, 1, 2, target); + expected += 2; + } else { + actual += wfunc(begin_obj, 1, 1, target); + expected++; + } + depth++; + size_t elem_count = value->value.object.values_size; + for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { + // get the member either via index array or directly + size_t elem_idx = settings->sort_members + ? look_idx + : value->value.object.indices[look_idx]; + CxJsonObjValue *member = &value->value.object.values[elem_idx]; + if (settings->sort_members) { + depth++;depth--; + } + + // possible indentation + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) { + return 1; // LCOV_EXCL_LINE + } + } + + // the name + actual += wfunc("\"", 1, 1, target); + // TODO: escape the string + actual += wfunc(member->name.ptr, 1, + member->name.length, target); + actual += wfunc("\"", 1, 1, target); + const char *obj_name_sep = ": "; + if (settings->pretty) { + actual += wfunc(obj_name_sep, 1, 2, target); + expected += 4 + member->name.length; + } else { + actual += wfunc(obj_name_sep, 1, 1, target); + expected += 3 + member->name.length; + } + + // the value + if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; + + // end of object-value + if (look_idx < elem_count - 1) { + const char *obj_value_sep = ",\n"; + if (settings->pretty) { + actual += wfunc(obj_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(obj_value_sep, 1, 1, target); + expected++; + } + } else { + if (settings->pretty) { + actual += wfunc("\n", 1, 1, target); + expected ++; + } + } + } + depth--; + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1; + } + actual += wfunc("}", 1, 1, target); + expected++; + break; + } + case CX_JSON_ARRAY: { + actual += wfunc("[", 1, 1, target); + expected++; + CxIterator iter = cxJsonArrIter(value); + cx_foreach(CxJsonValue*, element, iter) { + if (cx_json_write_rec( + target, element, + wfunc, settings, depth) + ) return 1; + + if (iter.index < iter.elem_count - 1) { + const char *arr_value_sep = ", "; + if (settings->pretty) { + actual += wfunc(arr_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(arr_value_sep, 1, 1, target); + expected++; + } + } + } + actual += wfunc("]", 1, 1, target); + expected++; + break; + } + case CX_JSON_STRING: { + actual += wfunc("\"", 1, 1, target); + // TODO: escape the string + actual += wfunc(value->value.string.ptr, 1, + value->value.string.length, target); + actual += wfunc("\"", 1, 1, target); + expected += 2 + value->value.string.length; + break; + } + case CX_JSON_NUMBER: { + // TODO: locale bullshit + // TODO: formatting settings + snprintf(numbuf, 32, "%g", value->value.number); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 1, len, target); + expected += len; + break; + } + case CX_JSON_INTEGER: { + snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 1, len, target); + expected += len; + break; + } + case CX_JSON_LITERAL: { + if (value->value.literal == CX_JSON_TRUE) { + actual += wfunc("true", 1, 4, target); + expected += 4; + } else if (value->value.literal == CX_JSON_FALSE) { + actual += wfunc("false", 1, 5, target); + expected += 5; + } else { + actual += wfunc("null", 1, 4, target); + expected += 4; + } + break; + } + case CX_JSON_NOTHING: { + // deliberately supported as an empty string! + // users might want to just write the result + // of a get operation without testing the value + // and therefore this should not blow up + break; + } + default: assert(false); // LCOV_EXCL_LINE + } + + return expected != actual; +} + +int cxJsonWrite( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings +) { + if (settings == NULL) { + settings = &cx_json_writer_default; + } + assert(target != NULL); + assert(value != NULL); + assert(wfunc != NULL); + + return cx_json_write_rec(target, value, wfunc, settings, 0); +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/linked_list.c --- a/ucx/linked_list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/linked_list.c Mon Jan 06 22:22:55 2025 +0100 @@ -27,7 +27,6 @@ */ #include "cx/linked_list.h" -#include "cx/utils.h" #include "cx/compare.h" #include #include @@ -91,7 +90,7 @@ do { void *current = ll_data(node); if (cmp_func(current, elem) == 0) { - *result = (void*) node; + *result = (void *) node; return index; } node = ll_advance(node); @@ -336,19 +335,22 @@ } } -void cx_linked_list_remove( +size_t cx_linked_list_remove_chain( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, - void *node + void *node, + size_t num ) { assert(node != NULL); assert(loc_next >= 0); assert(loc_prev >= 0 || begin != NULL); + // easy exit + if (num == 0) return 0; + // find adjacent nodes - void *next = ll_next(node); void *prev; if (loc_prev >= 0) { prev = ll_prev(node); @@ -356,6 +358,12 @@ prev = cx_linked_list_prev(*begin, loc_next, node); } + void *next = ll_next(node); + size_t removed = 1; + for (; removed < num && next != NULL ; removed++) { + next = ll_next(next); + } + // update next pointer of prev node, or set begin if (prev == NULL) { if (begin != NULL) { @@ -373,6 +381,8 @@ } else if (loc_prev >= 0) { ll_prev(next) = prev; } + + return removed; } size_t cx_linked_list_size( @@ -436,13 +446,13 @@ // Update pointer if (loc_prev >= 0) ll_prev(sorted[0]) = NULL; - cx_for_n (i, length - 1) { + for (size_t i = 0 ; i < length - 1; i++) { cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next); } ll_next(sorted[length - 1]) = NULL; *begin = sorted[0]; - *end = sorted[length-1]; + *end = sorted[length - 1]; if (sorted != sbo) { free(sorted); } @@ -646,9 +656,7 @@ cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert - if (0 != cx_ll_insert_at(list, node, array)) { - return 1; - } + if (0 != cx_ll_insert_at(list, node, array)) return 1; // is there more? if (n == 1) return 1; @@ -660,9 +668,7 @@ const char *source = array; for (size_t i = 1; i < n; i++) { source += list->collection.elem_size; - if (0 != cx_ll_insert_at(list, node, source)) { - return i; - } + if (0 != cx_ll_insert_at(list, node, source)) return i; node = node->next; } return n; @@ -733,30 +739,61 @@ return inserted; } -static int cx_ll_remove( +static size_t cx_ll_remove( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ) { cx_linked_list *ll = (cx_linked_list *) list; cx_linked_list_node *node = cx_ll_node_at(ll, index); // out-of-bounds check - if (node == NULL) return 1; - - // element destruction - cx_invoke_destructor(list, node->payload); + if (node == NULL) return 0; // remove - cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + size_t removed = cx_linked_list_remove_chain( + (void **) &ll->begin, + (void **) &ll->end, + CX_LL_LOC_PREV, + CX_LL_LOC_NEXT, + node, + num + ); // adjust size - list->collection.size--; + list->collection.size -= removed; + + // copy or destroy the removed chain + if (targetbuf == NULL) { + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // element destruction + cx_invoke_destructor(list, n->payload); - // free and return - cxFree(list->collection.allocator, node); + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } else { + char *dest = targetbuf; + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // copy payload + memcpy(dest, n->payload, list->collection.elem_size); - return 0; + // advance target buffer + dest += list->collection.elem_size; + + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } + + return removed; } static void cx_ll_clear(struct cx_list_s *list) { @@ -777,7 +814,7 @@ #ifndef CX_LINKED_LIST_SWAP_SBO_SIZE #define CX_LINKED_LIST_SWAP_SBO_SIZE 128 #endif -unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; +const unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; static int cx_ll_swap( struct cx_list_s *list, @@ -798,7 +835,7 @@ left = j; right = i; } - cx_linked_list_node *nleft, *nright; + cx_linked_list_node *nleft = NULL, *nright = NULL; if (left < mid && right < mid) { // case 1: both items left from mid nleft = cx_ll_node_at(ll, left); diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/list.c --- a/ucx/list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/list.c Mon Jan 06 22:22:55 2025 +0100 @@ -59,7 +59,7 @@ } static void cx_pl_destructor(struct cx_list_s *list) { - list->climpl->destructor(list); + list->climpl->deallocate(list); } static int cx_pl_insert_element( @@ -99,11 +99,13 @@ return list->climpl->insert_iter(iter, &elem, prepend); } -static int cx_pl_remove( +static size_t cx_pl_remove( struct cx_list_s *list, - size_t index + size_t index, + size_t num, + void *targetbuf ) { - return list->climpl->remove(list, index); + return list->climpl->remove(list, index, num, targetbuf); } static void cx_pl_clear(struct cx_list_s *list) { @@ -210,33 +212,33 @@ // -static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) { +static void cx_emptyl_noop(cx_attr_unused CxList *list) { // this is a noop, but MUST be implemented } static void *cx_emptyl_at( - __attribute__((__unused__)) const struct cx_list_s *list, - __attribute__((__unused__)) size_t index + cx_attr_unused const struct cx_list_s *list, + cx_attr_unused size_t index ) { return NULL; } static ssize_t cx_emptyl_find_remove( - __attribute__((__unused__)) struct cx_list_s *list, - __attribute__((__unused__)) const void *elem, - __attribute__((__unused__)) bool remove + cx_attr_unused struct cx_list_s *list, + cx_attr_unused const void *elem, + cx_attr_unused bool remove ) { return -1; } -static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_emptyl_iterator( const struct cx_list_s *list, size_t index, - __attribute__((__unused__)) bool backwards + cx_attr_unused bool backwards ) { CxIterator iter = {0}; iter.src_handle.c = list; @@ -295,10 +297,9 @@ const char *src = data; size_t i = 0; for (; i < n; i++) { - if (0 != invoke_list_func(insert_element, - list, index + i, src + (i * elem_size))) { - return i; - } + if (0 != invoke_list_func( + insert_element, list, index + i, + src + (i * elem_size))) return i; } return i; } @@ -343,9 +344,8 @@ // insert the elements at location si if (ins == 1) { - if (0 != invoke_list_func(insert_element, - list, di, src)) - return inserted; + if (0 != invoke_list_func( + insert_element, list, di, src)) return inserted; } else { size_t r = invoke_list_func(insert_array, list, di, src, ins); if (r < ins) return inserted + r; @@ -417,10 +417,6 @@ return 0; } -void cxListDestroy(CxList *list) { - list->cl->destructor(list); -} - int cxListCompare( const CxList *list, const CxList *other @@ -485,3 +481,8 @@ it.base.mutating = true; return it; } + +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/map.c --- a/ucx/map.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/map.c Mon Jan 06 22:22:55 2025 +0100 @@ -31,24 +31,24 @@ // -static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) { +static void cx_empty_map_noop(cx_attr_unused CxMap *map) { // this is a noop, but MUST be implemented } static void *cx_empty_map_get( - __attribute__((__unused__)) const CxMap *map, - __attribute__((__unused__)) CxHashKey key + cx_attr_unused const CxMap *map, + cx_attr_unused CxHashKey key ) { return NULL; } -static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_empty_map_iterator( const struct cx_map_s *map, - __attribute__((__unused__)) enum cx_map_iterator_type type + cx_attr_unused enum cx_map_iterator_type type ) { CxIterator iter = {0}; iter.src_handle.c = map; @@ -100,3 +100,8 @@ it.base.mutating = true; return it; } + +void cxMapFree(CxMap *map) { + if (map == NULL) return; + map->cl->deallocate(map); +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/mempool.c --- a/ucx/mempool.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/mempool.c Mon Jan 06 22:22:55 2025 +0100 @@ -27,8 +27,9 @@ */ #include "cx/mempool.h" -#include "cx/utils.h" + #include +#include 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; } @@ -119,7 +119,7 @@ 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); @@ -134,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); @@ -158,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; @@ -199,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; } diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/printf.c --- a/ucx/printf.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/printf.c Mon Jan 06 22:22:55 2025 +0100 @@ -34,7 +34,7 @@ #ifndef CX_PRINTF_SBO_SIZE #define CX_PRINTF_SBO_SIZE 512 #endif -unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; +const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; int cx_fprintf( void *stream, @@ -69,10 +69,10 @@ } else { int len = ret + 1; char *newbuf = malloc(len); - if (!newbuf) { + if (!newbuf) { // LCOV_EXCL_START va_end(ap2); return -1; - } + } // LCOV_EXCL_STOP ret = vsnprintf(newbuf, len, fmt, ap2); va_end(ap2); diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/properties.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/properties.c Mon Jan 06 22:22:55 2025 +0100 @@ -0,0 +1,406 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/properties.h" + +#include + +const CxPropertiesConfig cx_properties_config_default = { + '=', + //'\\', + '#', + '\0', + '\0' +}; + +void cxPropertiesInit( + CxProperties *prop, + CxPropertiesConfig config +) { + memset(prop, 0, sizeof(CxProperties)); + prop->config = config; +} + +void cxPropertiesDestroy(CxProperties *prop) { + cxBufferDestroy(&prop->input); + cxBufferDestroy(&prop->buffer); +} + +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +) { + if (cxBufferEof(&prop->input)) { + // destroy a possible previously initialized buffer + cxBufferDestroy(&prop->input); + cxBufferInit(&prop->input, (void*) buf, len, + NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + prop->input.size = len; + } else { + if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; + } + return 0; +} + +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +) { + cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); +} + +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +) { + // check if we have a text buffer + if (prop->input.space == NULL) { + return CX_PROPERTIES_NULL_INPUT; + } + + // a pointer to the buffer we want to read from + CxBuffer *current_buffer = &prop->input; + + // check if we have rescued data + if (!cxBufferEof(&prop->buffer)) { + // check if we can now get a complete line + cxstring input = cx_strn(prop->input.space + prop->input.pos, + prop->input.size - prop->input.pos); + cxstring nl = cx_strchr(input, '\n'); + if (nl.length > 0) { + // we add as much data to the rescue buffer as we need + // to complete the line + size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; + + if (cxBufferAppend(input.ptr, 1, + len_until_nl, &prop->buffer) < len_until_nl) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + + // advance the position in the input buffer + prop->input.pos += len_until_nl; + + // we now want to read from the rescue buffer + current_buffer = &prop->buffer; + } else { + // still not enough data, copy input buffer to internal buffer + if (cxBufferAppend(input.ptr, 1, + input.length, &prop->buffer) < input.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; + } + } + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + + // get one line and parse it + while (!cxBufferEof(current_buffer)) { + const char *buf = current_buffer->space + current_buffer->pos; + size_t len = current_buffer->size - current_buffer->pos; + + /* + * First we check if we have at least one line. We also get indices of + * delimiter and comment chars + */ + size_t delimiter_index = 0; + size_t comment_index = 0; + bool has_comment = false; + + size_t i = 0; + char c = 0; + for (; i < len; i++) { + c = buf[i]; + if (c == comment1 || c == comment2 || c == comment3) { + if (comment_index == 0) { + comment_index = i; + has_comment = true; + } + } else if (c == delimiter) { + if (delimiter_index == 0 && !has_comment) { + delimiter_index = i; + } + } else if (c == '\n') { + break; + } + } + + if (c != '\n') { + // we don't have enough data for a line, use the rescue buffer + assert(current_buffer != &prop->buffer); + // make sure that the rescue buffer does not already contain something + assert(cxBufferEof(&prop->buffer)); + if (prop->buffer.space == NULL) { + // initialize a rescue buffer, if the user did not provide one + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } else { + // from a previous rescue there might be already read data + // reset the buffer to avoid unnecessary buffer extension + cxBufferReset(&prop->buffer); + } + if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; + } + + cxstring line = has_comment ? + cx_strn(buf, comment_index) : + cx_strn(buf, i); + // check line + if (delimiter_index == 0) { + // if line is not blank ... + line = cx_strtrim(line); + // ... either no delimiter found, or key is empty + if (line.length > 0) { + if (line.ptr[0] == delimiter) { + return CX_PROPERTIES_INVALID_EMPTY_KEY; + } else { + return CX_PROPERTIES_INVALID_MISSING_DELIMITER; + } + } else { + // skip blank line + // if it was the rescue buffer, return to the original buffer + if (current_buffer == &prop->buffer) { + // assert that the rescue buffer really does not contain more data + assert(current_buffer->pos + i + 1 == current_buffer->size); + // reset the rescue buffer, but don't destroy it! + cxBufferReset(&prop->buffer); + // continue with the input buffer + current_buffer = &prop->input; + } else { + // if it was the input buffer already, just advance the position + current_buffer->pos += i + 1; + } + continue; + } + } else { + cxstring k = cx_strn(buf, delimiter_index); + cxstring val = cx_strn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + k = cx_strtrim(k); + val = cx_strtrim(val); + if (k.length > 0) { + *key = k; + *value = val; + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; + } else { + return CX_PROPERTIES_INVALID_EMPTY_KEY; + } + } + // unreachable - either we returned or skipped a blank line + assert(false); + } + + // when we come to this point, all data must have been read + assert(cxBufferEof(&prop->buffer)); + assert(cxBufferEof(&prop->input)); + + return CX_PROPERTIES_NO_DATA; +} + +static int cx_properties_sink_map( + cx_attr_unused CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +) { + CxMap *map = sink->sink; + CxAllocator *alloc = sink->data; + cxmutstr v = cx_strdup_a(alloc, value); + int r = cx_map_put_cxstr(map, key, v.ptr); + if (r != 0) cx_strfree_a(alloc, &v); + return r; +} + +CxPropertiesSink cxPropertiesMapSink(CxMap *map) { + CxPropertiesSink sink; + sink.sink = map; + sink.data = cxDefaultAllocator; + sink.sink_func = cx_properties_sink_map; + return sink; +} + +static int cx_properties_read_string( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + if (prop->input.space == src->src) { + // when the input buffer already contains the string + // we have nothing more to provide + target->length = 0; + } else { + target->ptr = src->src; + target->length = src->data_size; + } + return 0; +} + +static int cx_properties_read_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + target->ptr = src->data_ptr; + target->length = fread(src->data_ptr, 1, src->data_size, src->src); + return ferror(src->src); +} + +static int cx_properties_read_init_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + src->data_ptr = malloc(src->data_size); + if (src->data_ptr == NULL) return 1; + return 0; +} + +static void cx_properties_read_clean_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + free(src->data_ptr); +} + +CxPropertiesSource cxPropertiesStringSource(cxstring str) { + CxPropertiesSource src; + src.src = (void*) str.ptr; + src.data_size = str.length; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = len; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrSource(const char *str) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = strlen(str); + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { + CxPropertiesSource src; + src.src = file; + src.data_size = chunk_size; + src.data_ptr = NULL; + src.read_func = cx_properties_read_file; + src.read_init_func = cx_properties_read_init_file; + src.read_clean_func = cx_properties_read_clean_file; + return src; +} + +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +) { + assert(source.read_func != NULL); + assert(sink.sink_func != NULL); + + // initialize reader + if (source.read_init_func != NULL) { + if (source.read_init_func(prop, &source)) { + return CX_PROPERTIES_READ_INIT_FAILED; + } + } + + // transfer the data from the source to the sink + CxPropertiesStatus status; + bool found = false; + while (true) { + // read input + cxstring input; + if (source.read_func(prop, &source, &input)) { + status = CX_PROPERTIES_READ_FAILED; + break; + } + + // no more data - break + if (input.length == 0) { + status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; + break; + } + + // set the input buffer and read the k/v-pairs + cxPropertiesFill(prop, input); + + CxPropertiesStatus kv_status; + do { + cxstring key, value; + kv_status = cxPropertiesNext(prop, &key, &value); + if (kv_status == CX_PROPERTIES_NO_ERROR) { + found = true; + if (sink.sink_func(prop, &sink, key, value)) { + kv_status = CX_PROPERTIES_SINK_FAILED; + } + } + } while (kv_status == CX_PROPERTIES_NO_ERROR); + + if (kv_status > CX_PROPERTIES_OK) { + status = kv_status; + break; + } + } + + if (source.read_clean_func != NULL) { + source.read_clean_func(prop, &source); + } + + return status; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/streams.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/streams.c Mon Jan 06 22:22:55 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); +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/string.c --- a/ucx/string.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/string.c Mon Jan 06 22:22:55 2025 +0100 @@ -25,19 +25,23 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - +#define CX_STR_IMPLEMENTATION #include "cx/string.h" -#include "cx/utils.h" #include #include #include - -#ifndef _WIN32 +#include +#include +#include +#include -#include // for strncasecmp() - -#endif // _WIN32 +#ifdef _WIN32 +#define cx_strcasecmp_impl _strnicmp +#else +#include +#define cx_strcasecmp_impl strncasecmp +#endif cxmutstr cx_mutstr(char *cstring) { return (cxmutstr) {cstring, strlen(cstring)}; @@ -61,11 +65,8 @@ return (cxstring) {cstring, length}; } -cxstring cx_strcast(cxmutstr str) { - return (cxstring) {str.ptr, str.length}; -} - void cx_strfree(cxmutstr *str) { + if (str == NULL) return; free(str->ptr); str->ptr = NULL; str->length = 0; @@ -75,6 +76,7 @@ const CxAllocator *alloc, cxmutstr *str ) { + if (str == NULL) return; cxFree(alloc, str->ptr); str->ptr = NULL; str->length = 0; @@ -89,8 +91,9 @@ va_list ap; va_start(ap, count); size_t size = 0; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring str = va_arg(ap, cxstring); + if (size > SIZE_MAX - str.length) errno = EOVERFLOW; size += str.length; } va_end(ap); @@ -106,33 +109,59 @@ ) { if (count == 0) return str; - cxstring *strings = calloc(count, sizeof(cxstring)); - if (!strings) abort(); + cxstring strings_stack[8]; + cxstring *strings; + if (count > 8) { + strings = calloc(count, sizeof(cxstring)); + if (strings == NULL) { + return (cxmutstr) {NULL, 0}; + } + } else { + strings = strings_stack; + } va_list ap; va_start(ap, count); // get all args and overall length + bool overflow = false; size_t slen = str.length; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = va_arg (ap, cxstring); strings[i] = s; + if (slen > SIZE_MAX - str.length) overflow = true; slen += s.length; } va_end(ap); + // abort in case of overflow + if (overflow) { + errno = EOVERFLOW; + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) { NULL, 0 }; + } + // reallocate or create new string + char *newstr; if (str.ptr == NULL) { - str.ptr = cxMalloc(alloc, slen + 1); + newstr = cxMalloc(alloc, slen + 1); } else { - str.ptr = cxRealloc(alloc, str.ptr, slen + 1); + newstr = cxRealloc(alloc, str.ptr, slen + 1); } - if (str.ptr == NULL) abort(); + if (newstr == NULL) { + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) {NULL, 0}; + } + str.ptr = newstr; // concatenate strings size_t pos = str.length; str.length = slen; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = strings[i]; memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; @@ -142,7 +171,9 @@ str.ptr[str.length] = '\0'; // free temporary array - free(strings); + if (strings != strings_stack) { + free(strings); + } return str; } @@ -193,7 +224,7 @@ ) { chr = 0xFF & chr; // TODO: improve by comparing multiple bytes at once - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { if (string.ptr[i] == chr) { return cx_strsubs(string, i); } @@ -236,7 +267,7 @@ #ifndef CX_STRSTR_SBO_SIZE #define CX_STRSTR_SBO_SIZE 512 #endif -unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; +const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; cxstring cx_strstr( cxstring haystack, @@ -434,10 +465,14 @@ cxstring s2 ) { if (s1.length == s2.length) { - return memcmp(s1.ptr, s2.ptr, s1.length); + return strncmp(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = strncmp(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = strncmp(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } @@ -447,14 +482,14 @@ cxstring s2 ) { if (s1.length == s2.length) { -#ifdef _WIN32 - return _strnicmp(s1.ptr, s2.ptr, s1.length); -#else - return strncasecmp(s1.ptr, s2.ptr, s1.length); -#endif + return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } @@ -556,13 +591,13 @@ } void cx_strlower(cxmutstr string) { - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { string.ptr[i] = (char) tolower(string.ptr[i]); } } void cx_strupper(cxmutstr string) { - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { string.ptr[i] = (char) toupper(string.ptr[i]); } } @@ -748,7 +783,7 @@ // if more delimiters are specified, check them now if (ctx->delim_more_count > 0) { - cx_for_n(i, ctx->delim_more_count) { + for (size_t i = 0; i < ctx->delim_more_count; i++) { cxstring d = cx_strstr(haystack, ctx->delim_more[i]); if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) { delim.ptr = d.ptr; @@ -784,3 +819,370 @@ ctx->delim_more = delim; ctx->delim_more_count = count; } + +#define cx_strtoX_signed_impl(rtype, rmin, rmax) \ + long long result; \ + if (cx_strtoll_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result < rmin || result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX); +} + +int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int, INT_MIN, INT_MAX); +} + +int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX); +} + +int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep) { + // strategy: parse as unsigned, check range, negate if required + bool neg = false; + size_t start_unsigned = 0; + + // trim already, to search for a sign character + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // test if we have a negative sign character + if (str.ptr[start_unsigned] == '-') { + neg = true; + start_unsigned++; + // must not be followed by positive sign character + if (str.length == 1 || str.ptr[start_unsigned] == '+') { + errno = EINVAL; + return -1; + } + } + + // now parse the number with strtoull + unsigned long long v; + cxstring ustr = start_unsigned == 0 ? str + : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned); + int ret = cx_strtoull_lc(ustr, &v, base, groupsep); + if (ret != 0) return ret; + if (neg) { + if (v - 1 > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = -(long long) v; + return 0; + } else { + if (v > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = (long long) v; + return 0; + } +} + +int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX); +} + +int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX); +} + +int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX); +} + +int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep) { + assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +} + +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep) { +#if SSIZE_MAX == INT32_MAX + return cx_strtoi32_lc(str, (int32_t*) output, base, groupsep); +#elif SSIZE_MAX == INT64_MAX + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +#else +#error "unsupported ssize_t size" +#endif +} + +#define cx_strtoX_unsigned_impl(rtype, rmax) \ + uint64_t result; \ + if (cx_strtou64_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX); +} + +int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned int, UINT_MAX); +} + +int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX); +} + +int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep) { + // some sanity checks + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + if (!(base == 2 || base == 8 || base == 10 || base == 16)) { + errno = EINVAL; + return -1; + } + if (groupsep == NULL) groupsep = ""; + + // find the actual start of the number + if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + if (str.length == 0) { + errno = EINVAL; + return -1; + } + } + size_t start = 0; + + // if base is 2 or 16, some leading stuff may appear + if (base == 2) { + if ((str.ptr[0] | 32) == 'b') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'b') { + start = 2; + } + } + } else if (base == 16) { + if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'x') { + start = 2; + } + } + } + + // check if there are digits left + if (start >= str.length) { + errno = EINVAL; + return -1; + } + + // now parse the number + unsigned long long result = 0; + for (size_t i = start; i < str.length; i++) { + // ignore group separators + if (strchr(groupsep, str.ptr[i])) continue; + + // determine the digit value of the character + unsigned char c = str.ptr[i]; + if (c >= 'a') c = 10 + (c - 'a'); + else if (c >= 'A') c = 10 + (c - 'A'); + else if (c >= '0') c = c - '0'; + else c = 255; + if (c >= base) { + errno = EINVAL; + return -1; + } + + // now combine the digit with what we already have + unsigned long right = (result & 0xff) * base + c; + unsigned long long left = (result >> 8) * base + (right >> 8); + if (left > (ULLONG_MAX >> 8)) { + errno = ERANGE; + return -1; + } + result = (left << 8) + (right & 0xff); + } + + *output = result; + return 0; +} + +int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX); +} + +int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX); +} + +int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX); +} + +int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep) { + assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms + return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep); +} + +int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep) { +#if SIZE_MAX == UINT32_MAX + return cx_strtou32_lc(str, (uint32_t*) output, base, groupsep); +#elif SIZE_MAX == UINT64_MAX + return cx_strtoull_lc(str, (unsigned long long *) output, base, groupsep); +#else +#error "unsupported size_t size" +#endif +} + +int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep) { + // use string to double and add a range check + double d; + int ret = cx_strtod_lc(str, &d, decsep, groupsep); + if (ret != 0) return ret; + // note: FLT_MIN is the smallest POSITIVE number that can be represented + double test = d < 0 ? -d : d; + if (test < FLT_MIN || test > FLT_MAX) { + errno = ERANGE; + return -1; + } + *output = (float) d; + return 0; +} + +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep) { + // TODO: overflow check + // TODO: increase precision + + // trim and check + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + double result = 0.; + int sign = 1; + + // check if there is a sign + if (str.ptr[0] == '-') { + sign = -1; + str.ptr++; + str.length--; + } else if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + } + + // there must be at least one char to parse + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // parse all digits until we find the decsep + size_t pos = 0; + do { + if (isdigit(str.ptr[pos])) { + result = result * 10 + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + + // already done? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // is the next char the decsep? + if (str.ptr[pos] == decsep) { + pos++; + // it may end with the decsep, if it did not start with it + if (pos == str.length) { + if (str.length == 1) { + errno = EINVAL; + return -1; + } else { + *output = result * sign; + return 0; + } + } + // parse everything until exponent or end + double factor = 1.; + do { + if (isdigit(str.ptr[pos])) { + factor *= 0.1; + result = result + factor * (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + } + + // no exponent? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // now the next separator MUST be the exponent separator + // and at least one char must follow + if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) { + errno = EINVAL; + return -1; + } + pos++; + + // check if we have a sign for the exponent + double factor = 10.; + if (str.ptr[pos] == '-') { + factor = .1; + pos++; + } else if (str.ptr[pos] == '+') { + pos++; + } + + // at least one digit must follow + if (pos == str.length) { + errno = EINVAL; + return -1; + } + + // parse the exponent + unsigned int exp = 0; + do { + if (isdigit(str.ptr[pos])) { + exp = 10 * exp + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + errno = EINVAL; + return -1; + } + } while (++pos < str.length); + + // apply the exponent by fast exponentiation + do { + if (exp & 1) { + result *= factor; + } + factor *= factor; + } while ((exp >>= 1) > 0); + + // store the result and exit + *output = result * sign; + return 0; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/szmul.c --- a/ucx/szmul.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/szmul.c Mon Jan 06 22:22:55 2025 +0100 @@ -26,6 +26,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "cx/common.h" + +#ifndef CX_SZMUL_BUILTIN int cx_szmul_impl( size_t a, size_t b, @@ -44,3 +47,4 @@ return 1; } } +#endif // CX_SZMUL_BUILTIN diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/tree.c --- a/ucx/tree.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/tree.c Mon Jan 06 22:22:55 2025 +0100 @@ -42,6 +42,13 @@ #define cx_tree_ptr_locations \ loc_parent, loc_children, loc_last_child, loc_prev, loc_next +#define cx_tree_node_layout(tree) \ + (tree)->loc_parent,\ + (tree)->loc_children,\ + (tree)->loc_last_child,\ + (tree)->loc_prev, \ + (tree)->loc_next + static void cx_tree_zero_pointers( void *node, ptrdiff_t loc_parent, @@ -51,7 +58,9 @@ ptrdiff_t loc_next ) { tree_parent(node) = NULL; - tree_prev(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } tree_next(node) = NULL; tree_children(node) = NULL; if (loc_last_child >= 0) { @@ -60,14 +69,18 @@ } void cx_tree_link( - void *restrict parent, - void *restrict node, + void *parent, + void *node, ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, ptrdiff_t loc_prev, ptrdiff_t loc_next ) { + assert(loc_parent >= 0); + assert(loc_children >= 0); + assert(loc_next >= 0); + void *current_parent = tree_parent(node); if (current_parent == parent) return; if (current_parent != NULL) { @@ -80,24 +93,43 @@ tree_last_child(parent) = node; } } else { + void *child; if (loc_last_child >= 0) { - void *child = tree_last_child(parent); - tree_prev(node) = child; - tree_next(child) = node; + child = tree_last_child(parent); tree_last_child(parent) = node; } else { - void *child = tree_children(parent); + child = tree_children(parent); void *next; while ((next = tree_next(child)) != NULL) { child = next; } + } + if (loc_prev >= 0) { tree_prev(node) = child; - tree_next(child) = node; } + tree_next(child) = node; } tree_parent(node) = parent; } +static void *cx_tree_node_prev( + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_next, + const void *node +) { + void *parent = tree_parent(node); + void *begin = tree_children(parent); + if (begin == node) return NULL; + const void *cur = begin; + const void *next; + while (1) { + next = tree_next(cur); + if (next == node) return (void *) cur; + cur = next; + } +} + void cx_tree_unlink( void *node, ptrdiff_t loc_parent, @@ -108,7 +140,15 @@ ) { if (tree_parent(node) == NULL) return; - void *left = tree_prev(node); + assert(loc_children >= 0); + assert(loc_next >= 0); + assert(loc_parent >= 0); + void *left; + if (loc_prev >= 0) { + left = tree_prev(node); + } else { + left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node); + } void *right = tree_next(node); void *parent = tree_parent(node); assert(left == NULL || tree_children(parent) != node); @@ -125,94 +165,95 @@ tree_last_child(parent) = left; } } else { - tree_prev(right) = left; + if (loc_prev >= 0) { + tree_prev(right) = left; + } } tree_parent(node) = NULL; - tree_prev(node) = NULL; tree_next(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } } int cx_tree_search( const void *root, + size_t depth, const void *node, cx_tree_search_func sfunc, void **result, ptrdiff_t loc_children, ptrdiff_t loc_next ) { - int ret; + // help avoiding bugs due to uninitialized memory + assert(result != NULL); *result = NULL; - // shortcut: compare root before doing anything else - ret = sfunc(root, node); + // remember return value for best match + int ret = sfunc(root, node); if (ret < 0) { - return ret; - } else if (ret == 0 || tree_children(root) == NULL) { - *result = (void*)root; + // not contained, exit + return -1; + } + *result = (void*) root; + // if root is already exact match, exit + if (ret == 0) { + return 0; + } + + // when depth is one, we are already done + if (depth == 1) { return ret; } - // create a working stack - CX_ARRAY_DECLARE(const void *, work); - cx_array_initialize(work, 32); + // special case: indefinite depth + if (depth == 0) { + depth = SIZE_MAX; + } + + // create an iterator + CxTreeIterator iter = cx_tree_iterator( + (void*) root, false, loc_children, loc_next + ); + + // skip root, we already handled it + cxIteratorNext(iter); - // add the children of root to the working stack - { - void *c = tree_children(root); - while (c != NULL) { - cx_array_simple_add(work, c); - c = tree_next(c); + // loop through the remaining tree + cx_foreach(void *, elem, iter) { + // investigate the current node + int ret_elem = sfunc(elem, node); + if (ret_elem == 0) { + // if found, exit the search + *result = (void *) elem; + ret = 0; + break; + } else if (ret_elem > 0 && ret_elem < ret) { + // new distance is better + *result = elem; + ret = ret_elem; + } else { + // not contained or distance is worse, skip entire subtree + cxTreeIteratorContinue(iter); + } + + // when we reached the max depth, skip the subtree + if (iter.depth == depth) { + cxTreeIteratorContinue(iter); } } - // remember a candidate for adding the data - // also remember the exact return code from sfunc - void *candidate = (void *) root; - int ret_candidate = ret; - - // process the working stack - while (work_size > 0) { - // pop element - const void *elem = work[--work_size]; - - // apply the search function - ret = sfunc(elem, node); + // dispose the iterator as we might have exited the loop early + cxTreeIteratorDispose(&iter); - if (ret == 0) { - // if found, exit the search - *result = (void *) elem; - work_size = 0; - break; - } else if (ret > 0) { - // if children might contain the data, add them to the stack - void *c = tree_children(elem); - while (c != NULL) { - cx_array_simple_add(work, c); - c = tree_next(c); - } - - // remember this node in case no child is suitable - if (ret < ret_candidate) { - candidate = (void *) elem; - ret_candidate = ret; - } - } - } - - // not found, but was there a candidate? - if (ret != 0 && candidate != NULL) { - ret = ret_candidate; - *result = candidate; - } - - // free the working queue and return - free(work); + assert(ret < 0 || *result != NULL); return ret; } int cx_tree_search_data( const void *root, + size_t depth, const void *data, cx_tree_search_data_func sfunc, void **result, @@ -221,7 +262,7 @@ ) { // it is basically the same implementation return cx_tree_search( - root, data, + root, depth, data, (cx_tree_search_func) sfunc, result, loc_children, loc_next); @@ -369,7 +410,7 @@ return iter->node; } -__attribute__((__nonnull__)) +cx_attr_nonnull static void cx_tree_visitor_enqueue_siblings( struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) { node = tree_next(node); @@ -531,6 +572,7 @@ void *match = NULL; int result = cx_tree_search( root, + 0, *cnode, sfunc, &match, @@ -595,6 +637,7 @@ cx_tree_add_look_around_retry: result = cx_tree_search( current_node, + 0, new_node, sfunc, &match, @@ -681,37 +724,6 @@ loc_prev, loc_next); } -static void cx_tree_default_destructor(CxTree *tree) { - if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) { - CxTreeIterator iter = tree->cl->iterator(tree, true); - cx_foreach(void *, node, iter) { - if (iter.exiting) { - if (tree->simple_destructor) { - tree->simple_destructor(node); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, node); - } - } - } - } - cxFree(tree->allocator, tree); -} - -static CxTreeIterator cx_tree_default_iterator( - CxTree *tree, - bool visit_on_exit -) { - return cx_tree_iterator( - tree->root, visit_on_exit, - tree->loc_children, tree->loc_next - ); -} - -static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) { - return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next); -} - static int cx_tree_default_insert_element( CxTree *tree, const void *data @@ -765,13 +777,15 @@ static void *cx_tree_default_find( CxTree *tree, const void *subtree, - const void *data + const void *data, + size_t depth ) { if (tree->root == NULL) return NULL; void *found; if (0 == cx_tree_search_data( subtree, + depth, data, tree->search_data, &found, @@ -785,12 +799,9 @@ } static cx_tree_class cx_tree_default_class = { - cx_tree_default_destructor, cx_tree_default_insert_element, cx_tree_default_insert_many, - cx_tree_default_find, - cx_tree_default_iterator, - cx_tree_default_visitor + cx_tree_default_find }; CxTree *cxTreeCreate( @@ -804,6 +815,13 @@ ptrdiff_t loc_prev, ptrdiff_t loc_next ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(create_func != NULL); + assert(search_func != NULL); + assert(search_data_func != NULL); + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; @@ -812,6 +830,7 @@ tree->node_create = create_func; tree->search = search_func; tree->search_data = search_data_func; + tree->simple_destructor = NULL; tree->advanced_destructor = (cx_destructor_func2) cxFree; tree->destructor_data = (void *) allocator; tree->loc_parent = loc_parent; @@ -825,6 +844,14 @@ return tree; } +void cxTreeFree(CxTree *tree) { + if (tree == NULL) return; + if (tree->root != NULL) { + cxTreeClear(tree); + } + cxFree(tree->allocator, tree); +} + CxTree *cxTreeCreateWrapped( const CxAllocator *allocator, void *root, @@ -834,6 +861,11 @@ ptrdiff_t loc_prev, ptrdiff_t loc_next ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(root != NULL); + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; @@ -856,6 +888,27 @@ return tree; } +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +) { + size_t loc_parent = tree->loc_parent; + if (tree_parent(child) == NULL) { + tree->size++; + } + cx_tree_link(parent, child, cx_tree_node_layout(tree)); +} + +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +) { + cx_tree_link(parent, child, cx_tree_node_layout(tree)); + tree->size++; +} + int cxTreeAddChild( CxTree *tree, void *parent, @@ -893,14 +946,16 @@ } size_t cxTreeDepth(CxTree *tree) { - CxTreeVisitor visitor = tree->cl->visitor(tree); + CxTreeVisitor visitor = cx_tree_visitor( + tree->root, tree->loc_children, tree->loc_next + ); while (cxIteratorValid(visitor)) { cxIteratorNext(visitor); } return visitor.depth; } -int cxTreeRemove( +int cxTreeRemoveNode( CxTree *tree, void *node, cx_tree_relink_func relink_func @@ -956,3 +1011,44 @@ cx_tree_unlink(node, cx_tree_node_layout(tree)); tree->size -= subtree_size; } + +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +) { + int result = cxTreeRemoveNode(tree, node, relink_func); + if (result == 0) { + if (tree->simple_destructor) { + tree->simple_destructor(node); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, node); + } + return 0; + } else { + return result; + } +} + +void cxTreeDestroySubtree(CxTree *tree, void *node) { + cx_tree_unlink(node, cx_tree_node_layout(tree)); + CxTreeIterator iter = cx_tree_iterator( + node, true, + tree->loc_children, tree->loc_next + ); + cx_foreach(void *, child, iter) { + if (iter.exiting) { + if (tree->simple_destructor) { + tree->simple_destructor(child); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, child); + } + } + } + tree->size -= iter.counter; + if (node == tree->root) { + tree->root = NULL; + } +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ucx/utils.c --- a/ucx/utils.c Thu Dec 12 20:01:43 2024 +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 diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/cocoa/GridLayout.m --- a/ui/cocoa/GridLayout.m Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/cocoa/GridLayout.m Mon Jan 06 22:22:55 2025 +0100 @@ -208,7 +208,7 @@ } - (void) dealloc { - cxListDestroy(_children); + cxListFree(_children); } @end diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/context.c --- a/ui/common/context.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/context.c Mon Jan 06 22:22:55 2025 +0100 @@ -100,6 +100,7 @@ if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) { CxIterator i = cxMapIterator(var_ctx->vars_unbound); cx_foreach(CxMapEntry*, entry, i) { + printf("attach %s\n", entry->key->data); UiVar *var = entry->value; UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) { @@ -163,7 +164,7 @@ ctx->detach_document2(ctx, doc); } - cxListDestroy(ls); + cxListFree(ls); } static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) { @@ -465,7 +466,7 @@ } UIEXPORT void ui_context_destroy(UiContext *ctx) { - cxMempoolDestroy(ctx->mp); + cxMempoolFree(ctx->mp); } @@ -534,7 +535,7 @@ uic_add_group_widget(ctx, widget, enable, groups); - cxListDestroy(groups); + cxListFree(groups); } size_t uic_group_array_size(const int *groups) { diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/document.c --- a/ui/common/document.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/document.c Mon Jan 06 22:22:55 2025 +0100 @@ -110,7 +110,7 @@ if(ctx->close_callback) { ctx->close_callback(&ev, ctx->close_data); } - cxMempoolDestroy(ctx->mp); + cxMempoolFree(ctx->mp); } } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/menu.c --- a/ui/common/menu.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/menu.c Mon Jan 06 22:22:55 2025 +0100 @@ -175,7 +175,7 @@ mitem_set_id(&item->item); item->item.prev = NULL; item->item.next = NULL; - item->item.type = UI_MENU_CHECK_ITEM; + item->item.type = UI_MENU_RADIO_ITEM; item->label = nl_strdup(args.label); item->stockid = nl_strdup(args.stockid); @@ -198,6 +198,7 @@ item->callback = args.onselect; item->userdata = args.onselectdata; item->varname = nl_strdup(args.varname); + item->addseparator = args.addseparator; add_item((UiMenuItemI*)item); } @@ -325,6 +326,6 @@ free_menuitem(m); m = next; } - cxListDestroy(builder->current); + cxListFree(builder->current); free(builder); } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/menu.h --- a/ui/common/menu.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/menu.h Mon Jan 06 22:22:55 2025 +0100 @@ -101,6 +101,7 @@ char *label; char *stockid; char *icon; + char *varname; ui_callback callback; void *userdata; int *groups; @@ -113,6 +114,7 @@ ui_callback callback; void *userdata; char *varname; + UiBool addseparator; }; diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/object.c --- a/ui/common/object.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/object.c Mon Jan 06 22:22:55 2025 +0100 @@ -87,7 +87,7 @@ ev.intval = 0; obj->ctx->close_callback(&ev, obj->ctx->close_data); } - cxMempoolDestroy(obj->ctx->mp); + cxMempoolFree(obj->ctx->mp); } UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) { diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/common/types.c --- a/ui/common/types.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/common/types.c Mon Jan 06 22:22:55 2025 +0100 @@ -117,7 +117,7 @@ } void ui_list_free(UiList *list) { - cxListDestroy(list->data); + cxListFree(list->data); free(list); } @@ -213,7 +213,7 @@ info->titles[i] = c->name; i++; } - cxListDestroy(cols); + cxListFree(cols); return info; } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/container.c --- a/ui/gtk/container.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/container.c Mon Jan 06 22:22:55 2025 +0100 @@ -946,6 +946,127 @@ +/* -------------------- ItemList Container -------------------- */ + +static void remove_item(void *data, void *item) { + UiGtkItemListContainer *ct = data; + UiObject *obj = item; + if(ct->remove_items) { + BOX_REMOVE(ct->widget, obj->widget); + } + uic_object_destroy(obj); +} + +static void update_itemlist(UiList *list, int c) { + UiGtkItemListContainer *ct = list->obj; + + CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + new_items->collection.advanced_destructor = remove_item; + new_items->collection.destructor_data = ct; + + // only create new widgets for new elements, so at first we have + // to find which elements are new + // check which elements in the list are already in the container + void *elm = list->first(list); + int j = 0; + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = NULL; + cxMapRemoveAndGet(ct->current_items, key, &item_obj); + if(item_obj) { + g_object_ref(G_OBJECT(item_obj->widget)); + BOX_REMOVE(ct->widget, item_obj->widget); + cxMapPut(new_items, key, item_obj); + } + elm = list->next(list); + j++; + } + + // ct->current_items only contains elements, that are not in the list + cxMapFree(ct->current_items); // calls destructor remove_item + ct->current_items = new_items; + + // add all items + int index = 0; + elm = list->first(list); + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = cxMapGet(ct->current_items, key); + if(item_obj) { + // re-add previously created widget + ui_box_container_add(ct->container, item_obj->widget, FALSE); + } else { + // create new widget and object for this list element + CxMempool *mp = cxBasicMempoolCreate(256); + const CxAllocator *a = mp->allocator; + UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->window = NULL; + obj->widget = ui_subcontainer_create( + ct->subcontainer, + obj, + ct->spacing, + ct->columnspacing, + ct->rowspacing, + ct->margin); + ui_box_container_add(ct->container, obj->widget, FALSE); + if(ct->create_ui) { + ct->create_ui(obj, index, elm, ct->userdata); + } + cxMapPut(new_items, key, obj); + } + elm = list->next(list); + index++; + } +} + +static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) { + container->remove_items = FALSE; + cxMapFree(container->current_items); + free(container); +} + +UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args) { + UiObject *current = uic_current_obj(obj); + UiContainer *ct = current->container; + UI_APPLY_LAYOUT1(current, args); + + GtkWidget *box = args.container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing); + ui_set_name_and_style(box, args.name, args.style_class); + GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box; + ct->add(ct, widget, TRUE); + + UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer)); + container->parent = obj; + container->widget = box; + container->container = ui_box_container(current, box, args.container); + container->create_ui = args.create_ui; + container->userdata = args.userdata; + container->subcontainer = args.subcontainer; + container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + container->current_items->collection.advanced_destructor = remove_item; + container->current_items->collection.destructor_data = container; + container->margin = args.sub_margin; + container->spacing = args.sub_spacing; + container->columnspacing = args.sub_columnspacing; + container->rowspacing = args.sub_rowspacing; + container->remove_items = TRUE; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = container; + list->update = update_itemlist; + update_itemlist(list, 0); + } + g_signal_connect( + box, + "destroy", + G_CALLBACK(destroy_itemlist_container), + container); + + return box; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/container.h --- a/ui/gtk/container.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/container.h Mon Jan 06 22:22:55 2025 +0100 @@ -34,6 +34,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -133,6 +134,21 @@ UiHeaderbarAlternative alternative; /* only used by fallback headerbar */ } UiHeaderbarContainer; +typedef struct UiGtkItemListContainer { + UiObject *parent; + GtkWidget *widget; + UiContainer *container; + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + UiSubContainerType subcontainer; + CxMap *current_items; + int margin; + int spacing; + int columnspacing; + int rowspacing; + bool remove_items; +} UiGtkItemListContainer; + GtkWidget* ui_gtk_vbox_new(int spacing); GtkWidget* ui_gtk_hbox_new(int spacing); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/dnd.c --- a/ui/gtk/dnd.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/dnd.c Mon Jan 06 22:22:55 2025 +0100 @@ -191,7 +191,7 @@ } void ui_dnd_free(UiDnD *dnd) { - cxListDestroy(dnd->providers); + cxListFree(dnd->providers); free(dnd); } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/list.c --- a/ui/gtk/list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/list.c Mon Jan 06 22:22:55 2025 +0100 @@ -48,6 +48,527 @@ return column == 0 ? elm : NULL; } +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +#if GTK_CHECK_VERSION(4, 10, 0) + + +/* BEGIN GObject wrapper for generic pointers */ + +typedef struct _ObjWrapper { + GObject parent_instance; + void *data; +} ObjWrapper; + +typedef struct _ObjWrapperClass { + GObjectClass parent_class; +} ObjWrapperClass; + +G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) + +static void obj_wrapper_class_init(ObjWrapperClass *klass) { + +} + +static void obj_wrapper_init(ObjWrapper *self) { + self->data = NULL; +} + +ObjWrapper* obj_wrapper_new(void* data) { + ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); + obj->data = data; + return obj; +} + +/* END GObject wrapper for generic pointers */ + +static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *image = gtk_image_new(); + GtkWidget *label = gtk_label_new(NULL); + BOX_ADD(hbox, image); + BOX_ADD(hbox, label); + gtk_list_item_set_child(item, hbox); + g_object_set_data(G_OBJECT(hbox), "image", image); + g_object_set_data(G_OBJECT(hbox), "label", label); + } else if(type == UI_ICON) { + GtkWidget *image = gtk_image_new(); + gtk_list_item_set_child(item, image); + } else { + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(label), 0); + gtk_list_item_set_child(item, label); + } +} + +static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + + ObjWrapper *obj = gtk_list_item_get_item(item); + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + + void *data = model->getvalue(obj->data, col->data_column); + GtkWidget *child = gtk_list_item_get_child(item); + + bool freevalue = TRUE; + switch(type) { + case UI_STRING: { + freevalue = FALSE; + } + case UI_STRING_FREE: { + gtk_label_set_label(GTK_LABEL(child), data); + if(freevalue) { + free(data); + } + break; + } + case UI_INTEGER: { + intptr_t intvalue = (intptr_t)data; + char buf[32]; + snprintf(buf, 32, "%d", (int)intvalue); + gtk_label_set_label(GTK_LABEL(child), buf); + break; + } + case UI_ICON: { + UiIcon *icon = data; + if(icon) { + gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); + } + break; + } + case UI_ICON_TEXT: { + freevalue = FALSE; + } + case UI_ICON_TEXT_FREE: { + void *data2 = model->getvalue(obj->data, col->data_column+1); + GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); + GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); + if(data && image) { + UiIcon *icon = data; + gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); + } + if(data2 && label) { + gtk_label_set_label(GTK_LABEL(label), data2); + } + if(freevalue) { + free(data2); + } + break; + } + } +} + +static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { + GtkSelectionModel *selection_model; + if(multiselection) { + selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); + } else { + selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); + } + g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); + return selection_model; +} + +static UiListView* create_listview(UiObject *obj, UiListArgs args) { + UiListView *tableview = malloc(sizeof(UiListView)); + memset(tableview, 0, sizeof(UiListView)); + tableview->obj = obj; + tableview->model = args.model; + tableview->onactivate = args.onactivate; + tableview->onactivatedata = args.onactivatedata; + tableview->onselection = args.onselection; + tableview->onselectiondata = args.onselectiondata; + tableview->ondragstart = args.ondragstart; + tableview->ondragstartdata = args.ondragstartdata; + tableview->ondragcomplete = args.ondragcomplete; + tableview->ondragcompletedata = args.ondragcompletedata; + tableview->ondrop = args.ondrop; + tableview->ondropdata = args.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + return tableview; +} + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_table_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkSelectionModel *selection_model = create_selection_model(listview, ls, args.multiselection); + GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + // columnview and listview can use the same callback function, because + // the first parameter (which is technically a different pointer type) + // is ignored + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_tableview_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); + gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, view, FALSE); + return view; +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + //g_list_store_append(ls, v1); + + // create obj to store all relevant data we need for handling events + // and list updates + UiListView *tableview = create_listview(obj, args); + + GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection); + GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init tableview + tableview->widget = view; + tableview->var = var; + tableview->liststore = ls; + tableview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + + // create columns from UiModel + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + tableview->columns = calloc(columns, sizeof(UiColData)); + + int addi = 0; + for(int i=0;icolumns[i].listview = tableview; + tableview->columns[i].model_column = i; + tableview->columns[i].data_column = i+addi; + + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + // icon+text has 2 data columns + addi++; + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + UiColData *col = &tableview->columns[i]; + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); + } + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = tableview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { + UiListSelection sel = { 0, NULL }; + GtkBitset *bitset = gtk_selection_model_get_selection(model); + int n = gtk_bitset_get_size(bitset); + printf("bitset %d\n", n); + + gtk_bitset_unref(bitset); + return sel; +} + +static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + if(cb) { + cb(&event, cbdata); + } +} + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { + UiListView *view = userdata; + listview_event(view->onactivate, view->onactivatedata, view); +} + +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { + UiListView *view = userdata; + free(view->selection.rows); + view->selection.count = 0; + view->selection.rows = NULL; + + CX_ARRAY_DECLARE(int, newselection); + cx_array_initialize(newselection, 8); + + size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); + + for(size_t i=0;iselectionmodel, i)) { + int s = (int)i; + cx_array_simple_add(newselection, s); + } + } + + if(newselection_size > 0) { + view->selection.count = newselection_size; + view->selection.rows = newselection; + } else { + free(newselection); + } + + listview_event(view->onselection, view->onselectiondata, view); +} + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) { + UiListView *view = userdata; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + int sel2 = (int)selection; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = &sel2; + } + + if(view->onactivate) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + view->onactivate(&event, view->onactivatedata); + } +} + +void ui_update_liststore(GListStore *liststore, UiList *list) { + g_list_store_remove_all(liststore); + void *elm = list->first(list); + while(elm) { + ObjWrapper *obj = obj_wrapper_new(elm); + g_list_store_append(liststore, obj); + elm = list->next(list); + } +} + +void ui_listview_update2(UiList *list, int i) { + UiListView *view = list->obj; + ui_update_liststore(view->liststore, view->var->value); +} + +UiListSelection ui_listview_getselection2(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection; + selection.count = view->selection.count; + selection.rows = calloc(selection.count, sizeof(int)); + memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); + return selection; +} + +void ui_listview_setselection2(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + UiListSelection newselection; + newselection.count = view->selection.count; + if(selection.count > 0) { + newselection.rows = calloc(newselection.count, sizeof(int)); + memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); + } else { + newselection.rows = NULL; + } + free(view->selection.rows); + view->selection = newselection; + + gtk_selection_model_unselect_all(view->selectionmodel); + if(selection.count > 0) { + for(int i=0;iselectionmodel, selection.rows[i], FALSE); + } + } +} + +UiListSelection ui_combobox_getselection(UiList *list) { + UiListView *view = list->obj; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = (int)selection; + } + return sel; +} + +void ui_combobox_setselection(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + if(selection.count > 0) { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]); + } else { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION); + } +} + +#else + static GtkListStore* create_list_store(UiList *list, UiModel *model) { int columns = model->columns; GType types[2*columns]; @@ -199,6 +720,8 @@ listview->widget = view; listview->var = var; listview->model = model; + listview->selection.count = 0; + listview->selection.rows = NULL; g_signal_connect( view, "destroy", @@ -264,32 +787,6 @@ return scroll_area; } -/* -static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { - printf("drag begin\n"); - -} - -static void drag_end( - GtkWidget *widget, - GdkDragContext *context, - guint time, - gpointer udata) -{ - printf("drag end\n"); - -} -*/ - -/* -static GtkTargetEntry targetentries[] = - { - { "STRING", 0, 0 }, - { "text/plain", 0, 1 }, - { "text/uri-list", 0, 2 }, - }; -*/ - UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { UiObject* current = uic_current_obj(obj); @@ -376,6 +873,8 @@ tableview->ondragcompletedata = args.ondragcompletedata; tableview->ondrop = args.ondrop; tableview->ondropdata = args.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; g_signal_connect( view, "destroy", @@ -453,6 +952,225 @@ return scroll_area; } + + +void ui_listview_update(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(list, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), + NULL); + return selection; +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); + GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); + gtk_tree_selection_select_path(sel, path); + //g_object_unref(path); +} + + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); + ui_set_name_and_style(combobox, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, combobox, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, combobox, FALSE); + current->container->current = combobox; + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new(); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + } + + uicbox->var = var; + uicbox->model = model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + if(list) { + list->update = ui_combobox_modelupdate; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + list->obj = uicbox; + } + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); + gtk_cell_layout_set_attributes( + GTK_CELL_LAYOUT(combobox), + renderer, + "text", + 0, + NULL); + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view->var->value, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(store); +} + +UiListSelection ui_combobox_getselection(UiList *list) { + UiListView *combobox = list->obj; + UiListSelection ret; + ret.rows = malloc(sizeof(int*)); + ret.count = 1; + ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); + return ret; +} + +void ui_combobox_setselection(UiList *list, UiListSelection selection) { + UiListView *combobox = list->obj; + if(selection.count > 0) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); + } +} + + + + +void ui_listview_activate_event( + GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(treeview), + event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->activate(&e, event->activatedata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection(treeselection, event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->selection(&e, event->selectiondata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection ls; + ls.count = g_list_length(rows); + ls.rows = calloc(ls.count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls.rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +#endif + + #if GTK_MAJOR_VERSION >= 4 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { @@ -762,33 +1480,14 @@ free(t); } */ - -void ui_listview_update(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(list, view->model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(G_OBJECT(store)); -} - -UiListSelection ui_listview_getselection(UiList *list) { - UiListView *view = list->obj; - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), - NULL); - return selection; -} - -void ui_listview_setselection(UiList *list, UiListSelection selection) { - UiListView *view = list->obj; - GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); - GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); - gtk_tree_selection_select_path(sel, path); - //g_object_unref(path); -} void ui_listview_destroy(GtkWidget *w, UiListView *v) { //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); ui_destroy_boundvar(v->obj->ctx, v->var); +#if GTK_CHECK_VERSION(4, 10, 0) + free(v->columns); +#endif + free(v->selection.rows); free(v); } @@ -798,204 +1497,17 @@ } -void ui_listview_activate_event( - GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(treeview), - event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->activate(&e, event->activatedata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -void ui_listview_selection_event( - GtkTreeSelection *treeselection, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection(treeselection, event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->selection(&e, event->selectiondata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -UiListSelection ui_listview_selection( - GtkTreeSelection *selection, - UiTreeEventData *event) -{ - GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); - - UiListSelection ls; - ls.count = g_list_length(rows); - ls.rows = calloc(ls.count, sizeof(int)); - GList *r = rows; - int i = 0; - while(r) { - GtkTreePath *path = r->data; - ls.rows[i] = ui_tree_path_list_index(path); - r = r->next; - i++; - } - return ls; -} - -int ui_tree_path_list_index(GtkTreePath *path) { - int depth = gtk_tree_path_get_depth(path); - if(depth == 0) { - fprintf(stderr, "UiError: treeview selection: depth == 0\n"); - return -1; - } - int *indices = gtk_tree_path_get_indices(path); - return indices[depth - 1]; -} - - -/* --------------------------- ComboBox --------------------------- */ - -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); - ui_set_name_and_style(combobox, args.name, args.style_class); - ui_set_widget_groups(obj->ctx, combobox, args.groups); - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, combobox, FALSE); - current->container->current = combobox; - return combobox; -} - -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { - GtkWidget *combobox = gtk_combo_box_new(); - - UiListView *uicbox = malloc(sizeof(UiListView)); - uicbox->obj = obj; - uicbox->widget = combobox; - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - - if(listmodel) { - gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); - g_object_unref(listmodel); - } - - uicbox->var = var; - uicbox->model = model; - - g_signal_connect( - combobox, - "destroy", - G_CALLBACK(ui_combobox_destroy), - uicbox); - - // bind var - if(list) { - list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; - list->obj = uicbox; - } - - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); - gtk_cell_layout_set_attributes( - GTK_CELL_LAYOUT(combobox), - renderer, - "text", - 0, - NULL); - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); - - // add callback - if(f) { - UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); - event->obj = obj; - event->userdata = udata; - event->callback = f; - event->value = 0; - event->customdata = NULL; - - g_signal_connect( - combobox, - "changed", - G_CALLBACK(ui_combobox_change_event), - event); - } - - return combobox; -} - -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { - UiEvent event; - event.obj = e->obj; - event.window = event.obj->window; - event.document = event.obj->ctx->document; - event.eventdata = NULL; - event.intval = gtk_combo_box_get_active(widget); - e->callback(&event, e->userdata); -} - -void ui_combobox_modelupdate(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(view->var->value, view->model); - gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(store); -} - -UiListSelection ui_combobox_getselection(UiList *list) { - UiListView *combobox = list->obj; - UiListSelection ret; - ret.rows = malloc(sizeof(int*)); - ret.count = 1; - ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); - return ret; -} - -void ui_combobox_setselection(UiList *list, UiListSelection selection) { - UiListView *combobox = list->obj; - if(selection.count > 0) { - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); - } -} - - /* ------------------------------ Source List ------------------------------ */ static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { - cxListDestroy(v->sublists); + cxListFree(v->sublists); free(v); } static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { free(sublist->header); ui_destroy_boundvar(obj->ctx, sublist->var); - cxListDestroy(sublist->widgets); + cxListFree(sublist->widgets); } static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/list.h --- a/ui/gtk/list.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/list.h Mon Jan 06 22:22:55 2025 +0100 @@ -37,21 +37,39 @@ #ifdef __cplusplus extern "C" { #endif + +typedef struct UiColData UiColData; typedef struct UiListView { - UiObject *obj; - GtkWidget *widget; - UiVar *var; - UiModel *model; - ui_callback ondragstart; - void *ondragstartdata; - ui_callback ondragcomplete; - void *ondragcompletedata; - ui_callback ondrop; - void *ondropdata; + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; +#if GTK_CHECK_VERSION(4, 10, 0) + GListStore *liststore; + GtkSelectionModel *selectionmodel; + UiColData *columns; +#endif + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropdata; + UiListSelection selection; } UiListView; +struct UiColData { + UiListView *listview; + int model_column; + int data_column; +}; + typedef struct UiTreeEventData { UiObject *obj; ui_callback activate; @@ -86,6 +104,22 @@ GtkListBoxRow *first_row; }; + +#if GTK_CHECK_VERSION(4, 10, 0) + +void ui_update_liststore(GListStore *liststore, UiList *list); + +void ui_listview_update2(UiList *list, int i); +UiListSelection ui_listview_getselection2(UiList *list); +void ui_listview_setselection2(UiList *list, UiListSelection selection); + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata); +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data); + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata); + +#endif + void* ui_strmodel_getvalue(void *elm, int column); UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); @@ -100,6 +134,9 @@ void ui_combobox_destroy(GtkWidget *w, UiListView *v); void ui_listview_destroy(GtkWidget *w, UiListView *v); +#if GTK_CHECK_VERSION(4, 10, 0) + +#else void ui_listview_activate_event( GtkTreeView *tree_view, GtkTreePath *path, @@ -112,6 +149,7 @@ GtkTreeSelection *selection, UiTreeEventData *event); int ui_tree_path_list_index(GtkTreePath *path); +#endif void ui_listview_add_dnd(UiListView *listview, UiListArgs *args); void ui_listview_enable_drop(UiListView *listview, UiListArgs *args); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/menu.c --- a/ui/gtk/menu.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/menu.c Mon Jan 06 22:22:55 2025 +0100 @@ -131,7 +131,7 @@ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); cxListAddArray(groups, i->groups, i->ngroups); uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); - cxListDestroy(groups); + cxListFree(groups); } } @@ -390,7 +390,25 @@ #if GTK_MAJOR_VERSION >= 4 +GtkWidget *ui_create_menubar(UiObject *obj) { + UiMenu *menus_begin = uic_get_menu_list(); + if(menus_begin == NULL) { + return NULL; + } + + GMenu *menu = g_menu_new(); + UiMenu *ls = menus_begin; + while(ls) { + GMenu *sub_menu = g_menu_new(); + ui_gmenu_add_menu_items(sub_menu, 0, ls, obj); + g_menu_append_submenu(menu, ls->label, G_MENU_MODEL(sub_menu)); + ls = (UiMenu*)ls->item.next; + } + + // Create a menubar from the menu model + return gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(menu)); +} static ui_gmenu_add_f createMenuItem[] = { /* UI_MENU */ ui_gmenu_add_menu, @@ -446,7 +464,7 @@ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); cxListAddArray(groups, i->groups, i->ngroups); uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); - cxListDestroy(groups); + cxListFree(groups); } if(i->callback != NULL) { diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/menu.h --- a/ui/gtk/menu.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/menu.h Mon Jan 06 22:22:55 2025 +0100 @@ -42,6 +42,8 @@ UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj); void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu); +GtkWidget *ui_create_menubar(UiObject *obj); + #if GTK_MAJOR_VERSION <= 3 typedef struct UiActiveMenuItemList UiActiveMenuItemList; @@ -59,8 +61,6 @@ void *userdata; }; -GtkWidget *ui_create_menubar(UiObject *obj); - void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj); void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/toolkit.h --- a/ui/gtk/toolkit.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/toolkit.h Mon Jan 06 22:22:55 2025 +0100 @@ -61,6 +61,7 @@ #define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child) #define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child) #define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child) +#define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child) #define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text) #define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry)) #define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new() @@ -80,6 +81,7 @@ #define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child) #define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0) #define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0) +#define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child) #define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text) #define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry)) #define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL) @@ -152,15 +154,15 @@ void *userdata; } UiVarEventData; +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; + #ifndef UI_GTK4 struct UiSelection { GtkSelectionData *data; }; #endif -typedef enum UiOrientation UiOrientation; -enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; - #ifdef UI_APPLICATION void ui_app_quit(); GtkApplication* ui_get_application(); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/gtk/window.c --- a/ui/gtk/window.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/window.c Mon Jan 06 22:22:55 2025 +0100 @@ -195,6 +195,14 @@ #elif GTK_MAJOR_VERSION >= 4 GtkWidget *content_box = ui_gtk_vbox_new(0); WINDOW_SET_CONTENT(obj->widget, vbox); + if(!simple) { + if(uic_get_menu_list()) { + GtkWidget *mb = ui_create_menubar(obj); + if(mb) { + BOX_ADD(vbox, mb); + } + } + } if(sidebar) { GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/Grid.c --- a/ui/motif/Grid.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/Grid.c Mon Jan 06 22:22:55 2025 +0100 @@ -52,6 +52,39 @@ : getfocus()\n\ : loosefocus()\n"; +static XtResource resources[] = +{ + { + gridColumnSpacing, + gridColumnSpacing, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.columnspacing), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRowSpacing, + gridRowSpacing, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.rowspacing), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMargin, + gridMargin, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.margin), + XmRImmediate, + (XtPointer) 0 + } +}; ///* static XtResource constraints[] = @@ -175,11 +208,19 @@ grid.vfill), XmRImmediate, (XtPointer) 0 + }, + { + gridMinWidth, + gridMinWidth, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.min_width), + XmRImmediate, + (XtPointer) 0 } - }; //*/ -//static XtResource constraints[] = {}; GridClassRec gridClassRec = { // Core Class @@ -187,17 +228,17 @@ //(WidgetClass)&constraintClassRec, // superclass (WidgetClass)&xmManagerClassRec, "Grid", // class_name - sizeof(GridRec), // widget_size - grid_class_initialize, // class_initialize + sizeof(GridRec), // widget_size + grid_class_initialize, // class_initialize NULL, // class_part_initialize FALSE, // class_inited - (XtInitProc)grid_initialize, // initialize + (XtInitProc)grid_initialize, // initialize NULL, // initialize_hook - grid_realize, // realize - actionslist, // actions - XtNumber(actionslist), // num_actions - NULL, // resources - 0, // num_resources + grid_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources NULLQUARK, // xrm_class True, // compress_motion True, // compress_exposure @@ -240,12 +281,12 @@ // XmManager Class ///* { - NULL, + XtInheritTranslations, NULL, 0, NULL, 0, - NULL, + XmInheritParentProcess, NULL }, //*/ @@ -367,6 +408,8 @@ int req_width = 0; int req_height = 0; + //printf("container width: %d\n", (int)w->core.width); + // calculate the minimum size requirements for all columns and rows // we need to run this 2 times: for widgets without colspan/rowspan first // and then again for colspan/rowspan > 1 @@ -375,6 +418,15 @@ for(int i=0;icomposite.num_children;i++) { Widget child = w->composite.children[i]; GridConstraintRec *constraints = child->core.constraints; + if(constraints->grid.pref_width == 0) { + constraints->grid.pref_width = child->core.width; + } + if(constraints->grid.pref_height == 0) { + constraints->grid.pref_height = child->core.height; + } + if(constraints->grid.pref_width < constraints->grid.min_width) { + constraints->grid.pref_width = constraints->grid.min_width; + } if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) { continue; @@ -463,7 +515,7 @@ span_max = 50000; // not sure if this is unreasonable low or high } - + // calc required size for(int i=0;i 0 && req_height > 0) { + // add col/row spacing + req_width += (ncols-1)*w->mywidget.columnspacing; + req_height += (nrows-1)*w->mywidget.rowspacing; + Widget parent = w->core.parent; Dimension rwidth = req_width; Dimension rheight = req_height; @@ -491,6 +547,13 @@ if(!w->mywidget.sizerequest) { Dimension actual_width, actual_height; w->mywidget.sizerequest = TRUE; + + //XtWidgetGeometry request; + //request.width = req_width; + //request.request_mode = CWWidth; + //XtWidgetGeometry reply; + //XtGeometryResult result = XtMakeGeometryRequest((Widget)w, &request, &reply); + XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height); w->mywidget.sizerequest = FALSE; //printf("size request: %d %d\n", (int)actual_width, (int)actual_height); @@ -500,6 +563,7 @@ } + // how much space can we add to each expanding col/row int hexpand = 0; int width_diff = (int)w->core.width - req_width; int hexpand2 = 0; @@ -513,7 +577,7 @@ if(cols[i].expand) { cols[i].size += hexpand + hexpand2; } - x += cols[i].size; + x += cols[i].size + w->mywidget.columnspacing; hexpand2 = 0; } @@ -531,7 +595,7 @@ if(rows[i].expand) { rows[i].size += vexpand + vexpand2; } - y += rows[i].size; + y += rows[i].size += w->mywidget.rowspacing; vexpand2 = 0; } @@ -550,12 +614,12 @@ Dimension cwidth = 0; for(int j=0;jgrid.colspan;j++) { if(constraints->grid.x+j < ncols) { - cwidth += cols[constraints->grid.x+j].size; + cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->mywidget.columnspacing : 0); } } width = cwidth; } else { - width = c.size; + width = c.size - w->mywidget.columnspacing; } } if(constraints->grid.vfill) { @@ -563,16 +627,18 @@ Dimension cheight = 0; for(int j=0;jgrid.rowspan;j++) { if(constraints->grid.y+j < nrows) { - cheight += rows[constraints->grid.y+j].size; + cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->mywidget.rowspacing : 0); } } height = cheight; } else { - height = r.size; + height = r.size - w->mywidget.rowspacing; } } - XtConfigureWidget(child, x, y, width, height, child->core.border_width); + if(width > 0 && height > 0) { + XtConfigureWidget(child, x, y, width, height, child->core.border_width); + } //printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height); } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/Grid.h --- a/ui/motif/Grid.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/Grid.h Mon Jan 06 22:22:55 2025 +0100 @@ -40,6 +40,12 @@ extern "C" { #endif +// resources +#define gridColumnSpacing "gridColumnSpacing" +#define gridRowSpacing "gridRowSpacing" +#define gridMargin "gridMargin" + +// constraints #define gridColumn "gridColumn" #define gridRow "gridRow" #define gridColspan "gridColspan" @@ -52,6 +58,7 @@ #define gridMarginRight "gridMarginRight" #define gridMarginTop "gridMarginTop" #define gridMarginBottom "gridMarginBottom" +#define gridMinWidth "gridMinWidth" typedef struct GridDef { @@ -80,6 +87,9 @@ int margin_bottom; int max_col; int max_row; + Dimension columnspacing; + Dimension rowspacing; + Dimension margin; Boolean sizerequest; } GridPart; @@ -107,6 +117,7 @@ Dimension rowspan; Dimension pref_width; Dimension pref_height; + Dimension min_width; } GridContraintPart; typedef struct GridConstraintRec { diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/button.c --- a/ui/motif/button.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/button.c Mon Jan 06 22:22:55 2025 +0100 @@ -49,7 +49,7 @@ UI_APPLY_LAYOUT(ctn->layout, args); Widget parent = ctn->prepare(ctn, xargs, &n); - + XmString label = NULL; if(args.label) { label = XmStringCreateLocalized((char*)args.label); @@ -239,7 +239,7 @@ } static void destroy_list(Widget w, CxList *list, XtPointer d) { - cxListDestroy(list); + cxListFree(list); } static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) { @@ -291,6 +291,55 @@ } } +void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group) { + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + CxList *rb = value->obj; + if(!rb) { + // first button in the radiobutton group + // create a list for all buttons and use the list as value obj + rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + value->obj = rb; + value->get = ui_radiobutton_get; + value->set = ui_radiobutton_set; + + // the first radio button is also responsible for cleanup + XtAddCallback( + rbutton, + XmNdestroyCallback, + (XtCallbackProc)destroy_list, + rb); + } + cxListAdd(rb, rbutton); + + // set the radiobutton state, if the value is already set + if(cxListSize(rb) == value->value) { + XmToggleButtonSetState(rbutton, True, False); + } + } + + // the radio button needs to handle change events to update all + // other buttons in the radio button group + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = onchange; + event->userdata = onchangedata; + event->observers = NULL; + event->var = var; + event->value = enable_group; + XtAddCallback( + rbutton, + XmNvalueChangedCallback, + (XtCallbackProc)radiobutton_changed, + event); + XtAddCallback( + rbutton, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); +} + UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { Arg xargs[16]; int n = 0; diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/button.h --- a/ui/motif/button.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/button.h Mon Jan 06 22:22:55 2025 +0100 @@ -50,6 +50,8 @@ int64_t ui_togglebutton_get(UiInteger *i); void ui_togglebutton_set(UiInteger *i, int64_t value); +void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group); + int64_t ui_radiobutton_get(UiInteger *i); void ui_radiobutton_set(UiInteger *i, int64_t value); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/container.c --- a/ui/motif/container.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/container.c Mon Jan 06 22:22:55 2025 +0100 @@ -128,12 +128,13 @@ Arg xargs[16]; int n = 0; - XtSetArg(xargs[n], XmNbackground, 0); n++; - UiContainerPrivate *ctn = ui_obj_container(obj); UI_APPLY_LAYOUT(ctn->layout, args); Widget parent = ctn->prepare(ctn, xargs, &n); + XtSetArg(xargs[n], gridMargin, args.margin); n++; + XtSetArg(xargs[n], gridColumnSpacing, args.columnspacing); n++; + XtSetArg(xargs[n], gridRowSpacing, args.rowspacing); n++; Widget grid = XtCreateManagedWidget(args.name ? args.name : "gridcontainer", gridClass, parent, xargs, n); ctn->add(ctn, grid); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/label.c --- a/ui/motif/label.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/label.c Mon Jan 06 22:22:55 2025 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 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: @@ -34,3 +34,209 @@ #include "../common/context.h" #include "../common/object.h" +#include "Grid.h" + +static UIWIDGET label_create(UiObject *obj, UiLabelArgs args, int align) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + + XtSetArg(xargs[n], XmNalignment, align); n++; + XmString label = NULL; + if(args.label) { + label = XmStringCreateLocalized((char*)args.label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args.name ? (char*)args.name : "label"; + Widget w = XmCreateLabel(parent, name, xargs, n); + XtManageChild(w); + ctn->add(ctn, w); + + XmStringFree(label); + return w; +} + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) { + return label_create(obj, args, XmALIGNMENT_CENTER); +} + +UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) { + return label_create(obj, args, XmALIGNMENT_BEGINNING); +} + +UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) { + return label_create(obj, args, XmALIGNMENT_END); +} + + +/* -------------------------- progressbar/spiner -------------------------- */ + +static void ui_destroy_progressbar(Widget w, UiProgressBar *pb, XtPointer d) { + // TODO: free other stuff + free(pb); +} + +static void ui_progressbar_expose(Widget widget, UiProgressBar *pb, XtPointer c) { + Display *dp = XtDisplay(widget); + Window w = XtWindow(widget); + if(w == 0) { + return; + } + if(!pb->gc) { + XGCValues gcvals; + gcvals.foreground = pb->color; + pb->gc = XCreateGC(dp, w, (GCForeground), &gcvals); + } + + Dimension width = widget->core.width; + Dimension height = widget->core.height; + + double value = (pb->value - pb->min) / (pb->max - pb->min); + Dimension valueW = (double)width * value; + + XClearArea(dp, w, 0, 0, width, height, False); + XFillRectangle(dp, w, pb->gc, 0, 0, valueW, widget->core.height); +} + +UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + + char *name = args.name ? (char*)args.name : "progressbar"; + Widget frame = XmCreateFrame(parent, name, xargs, n); + + // create a button and get some informations about the height, shadow, highlight, .... + // we want the frame to have the same dimensions as a normal button + Widget test = XmCreatePushButton(frame, "button", NULL, 0); + XtManageChild(test); + Dimension h, highlightThickness, shadowThickness; + Pixel highlightColor; + XtVaGetValues(test, XmNheight, &h, XmNhighlightThickness, &highlightThickness, + XmNshadowThickness, &shadowThickness, XmNhighlightColor, &highlightColor, NULL); + XtDestroyWidget(test); + + // adjust frame + XtVaSetValues(frame, XmNshadowThickness, shadowThickness, gridMarginLeft, highlightThickness, + gridMarginRight, highlightThickness, gridMarginTop, highlightThickness, + gridMarginBottom, highlightThickness, NULL); + + // create drawing area + Dimension da_height = h - 2*highlightThickness - 2*shadowThickness; + n = 0; + XtSetArg(xargs[n], XmNheight, da_height); n++; + Widget drawingArea = XmCreateDrawingArea(frame, "progressbar_drawingarea", xargs, n); + XtManageChild(drawingArea); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_DOUBLE); + + UiProgressBar *progressbarData = malloc(sizeof(UiProgressBar)); + progressbarData->widget = drawingArea; + progressbarData->min = args.min; + progressbarData->max = args.max == 0 ? 100 : args.max; + progressbarData->value = 50; + progressbarData->var = var; + progressbarData->color = highlightColor; + progressbarData->gc = NULL; // initialize on first expose + + if(var) { + UiDouble *d = var->value; + progressbarData->value = d->value; + d->obj = progressbarData; + d->get = ui_progressbar_get; + d->set = ui_progressbar_set; + } + + XtAddCallback( + drawingArea, + XmNexposeCallback, + (XtCallbackProc)ui_progressbar_expose, + progressbarData); + XtAddCallback( + drawingArea, + XmNresizeCallback, + (XtCallbackProc)ui_progressbar_expose, + progressbarData); + + + XtManageChild(frame); + return frame; +} + +double ui_progressbar_get(UiDouble *d) { + UiProgressBar *pb = d->obj; + d->value = pb->value; + return d->value; +} + +void ui_progressbar_set(UiDouble *d, double value) { + UiProgressBar *pb = d->obj; + d->value = value; + pb->value = value; + ui_progressbar_expose(pb->widget, pb, NULL); +} + + +UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + + XmString label = XmStringCreateSimple(""); + XtSetArg(xargs[n], XmNlabelString, label); n++; + XtSetArg(xargs[n], XmNalignment, XmALIGNMENT_END); n++; + XtSetArg(xargs[n], gridMinWidth, 40); n++; + + char *name = args.name ? (char*)args.name : "progresss_spinner"; + Widget w = XmCreateLabel(parent, name, xargs, n); + XtManageChild(w); + ctn->add(ctn, w); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + value->obj = w; + value->get = ui_progressspinner_get; + value->set = ui_progressspinner_set; + + if(value->value) { + ui_progressspinner_set(value, 1); + } + } + + + XmStringFree(label); + return w; +} + +int64_t ui_progressspinner_get(UiInteger *i) { + return i->value; +} + +void ui_progressspinner_set(UiInteger *i, int64_t value) { + Widget w = i->obj; + XmString label; + if(value) { + char str[4]; + snprintf(str, 4, "%c", 150); + label = XmStringCreateSimple(str); + } else { + label = XmStringCreateSimple(""); + } + XtVaSetValues(w, XmNlabelString, label, NULL); + XmStringFree(label); + i->value = value; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/label.h --- a/ui/motif/label.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/label.h Mon Jan 06 22:22:55 2025 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 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: @@ -29,12 +29,29 @@ #ifndef LABEL_H #define LABEL_H +#include "../ui/display.h" +#include "../common/context.h" + #ifdef __cplusplus extern "C" { #endif +typedef struct UiProgressBar { + Widget widget; + GC gc; + UiVar *var; + double min; + double max; + double value; + Pixel color; +} UiProgressBar; +double ui_progressbar_get(UiDouble *d); +void ui_progressbar_set(UiDouble *d, double value); + +int64_t ui_progressspinner_get(UiInteger *i); +void ui_progressspinner_set(UiInteger *i, int64_t value); #ifdef __cplusplus } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/list.c --- a/ui/motif/list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/list.c Mon Jan 06 22:22:55 2025 +0100 @@ -34,3 +34,165 @@ #include "list.h" #include "../common/object.h" +UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + if(args.multiselection) { + XtSetArg(xargs[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++; + } else { + XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++; + } + + char *name = args.name ? (char*)args.name : "listview"; + Widget parent = ctn->prepare(ctn, xargs, &n); + Widget widget = XmCreateScrolledList(parent, name, xargs, n); + XtManageChild(widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.list, args.varname, UI_VAR_LIST); + + UiListView *listview = malloc(sizeof(UiListView)); + memset(listview, 0, sizeof(UiListView)); + listview->obj = obj; + listview->widget = widget; + listview->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + listview->var = var; + listview->onactivate = args.onactivate; + listview->onactivatedata = args.onactivatedata; + listview->onselection = args.onselection; + listview->onselectiondata = args.onselectiondata; + + if(var) { + UiList *list = var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + ui_listview_update(list, 0); + } + + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_listview_destroy, + listview); + + XtAddCallback( + widget, + XmNdefaultActionCallback, + (XtCallbackProc)ui_listview_activate, + listview); + XtAddCallback( + widget, + XmNextendedSelectionCallback, + (XtCallbackProc)ui_listview_selection, + listview); + XtAddCallback( + widget, + XmNsingleSelectionCallback, + (XtCallbackProc)ui_listview_selection, + listview); + + return widget; +} + +void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) { + // TODO +} + +static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = &sel; + event.intval = sel.count > 0 ? sel.rows[0] : -1; + callback(&event, userdata); +} + +static void listview_save_selection(UiListView *listview, XmListCallbackStruct *cb) { + UiListSelection sel = { cb->selected_item_count, NULL }; + if(sel.count > 0) { + sel.rows = calloc(sel.count, sizeof(int)); + for(int i=0;iselected_item_positions[i]-1; + } + } + free(listview->current_selection.rows); + listview->current_selection = sel; +} + +void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb) { + listview_save_selection(listview, cb); + if(listview->onactivate) { + list_callback(listview->obj, listview->current_selection, listview->onactivate, listview->onactivatedata); + } +} + +void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) { + listview_save_selection(listview, cb); + if(listview->onselection) { + list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata); + } +} + +static XmStringTable create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) { + int num = list->count(list); + XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString)); + void *data = list->first(list); + for(int i=0;inext(list); + } + + *count = num; + return items; +} + +void ui_listview_update(UiList *list, int i) { + UiListView *listview = list->obj; + + int count; + XmStringTable items = create_stringlist( + list, + listview->getvalue, + &count); + + XtVaSetValues( + listview->widget, + XmNitems, count == 0 ? NULL : items, + XmNitemCount, + count, + NULL); + + for (int i=0;iobj; + UiListSelection sel = { listview->current_selection.count, NULL }; + if(sel.count > 0) { + sel.rows = calloc(sel.count, sizeof(int)); + memcpy(sel.rows, listview->current_selection.rows, sel.count*sizeof(int)); + } + return sel; +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + UiListView *listview = list->obj; + XmListDeselectAllItems(listview->widget); + for(int i=0;iwidget, selection.rows[i]+1, False); + } +} + +void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/list.h --- a/ui/motif/list.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/list.h Mon Jan 06 22:22:55 2025 +0100 @@ -37,7 +37,38 @@ extern "C" { #endif +typedef struct UiListView { + UiObject *obj; + Widget widget; + UiVar *var; + UiModel* model; + ui_getvaluefunc getvalue; + UiListSelection current_selection; + + ui_callback onactivate; + void* onactivatedata; + ui_callback onselection; + void* onselectiondata; + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropsdata; + UiBool multiselection; +} UiListView; + +void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d); + +void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb); +void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); +void ui_listview_setselection(UiList *list, UiListSelection selection); + +void* ui_strmodel_getvalue(void *elm, int column); #ifdef __cplusplus } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/menu.c --- a/ui/motif/menu.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/menu.c Mon Jan 06 22:22:55 2025 +0100 @@ -36,8 +36,278 @@ #include "stock.h" #include "container.h" #include "../common/context.h" +#include "../common/menu.h" +#include "../common/types.h" #include "../ui/window.h" #include #include + +static ui_menu_add_f createMenuItem[] = { + /* UI_MENU */ add_menu_widget, + /* UI_MENU_ITEM */ add_menuitem_widget, + /* UI_MENU_CHECK_ITEM */ add_checkitem_widget, + /* UI_MENU_RADIO_ITEM */ add_radioitem_widget, + /* UI_MENU_ITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_CHECKITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_RADIOITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_SEPARATOR */ add_menuseparator_widget +}; + +void ui_create_menubar(UiObject *obj, Widget window) { + UiMenu *menus_begin = uic_get_menu_list(); + if(!menus_begin) { + return; + } + + Widget menubar = XmCreateMenuBar(window, "menubar", NULL, 0); + XtManageChild(menubar); + + UiMenu *ls = menus_begin; + while(ls) { + UiMenu *menu = ls; + add_menu_widget(menubar, 0, &menu->item, obj); + ls = (UiMenu*)ls->item.next; + } +} + +void ui_add_menu_items(Widget parent, int i, UiMenu *menu, UiObject *obj) { + UiMenuItemI *it = menu->items_begin; + int index = 0; + while(it) { + createMenuItem[it->type](parent, index, it, obj); + it = it->next; + index++; + } +} + +void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenu *menu = (UiMenu*)item; + Arg args[4]; + int n = 0; + + XmString s = NULL; + if(menu->label) { + s = XmStringCreateLocalized((char*)menu->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + Widget submenu = XmVaCreateSimplePulldownMenu(parent, "menu_pulldown", i, NULL, NULL); + XtSetArg(args[n], XmNsubMenuId, submenu); n++; + Widget menuItem = XtCreateManagedWidget( + "menuitem", + xmCascadeButtonWidgetClass, + parent, + args, + n); + + + if(s) { + XmStringFree(s); + } + + ui_add_menu_items(submenu, i, menu, obj); +} + +void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *it = (UiMenuItem*)item; + + XmString s = NULL; + Arg args[4]; + int n = 0; + if(it->label) { + s = XmStringCreateLocalized((char*)it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + parent, + args, + n); + if(s) { + XmStringFree(s); + } + + if(it->callback) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = it->callback; + eventdata->userdata = it->userdata; + eventdata->obj = obj; + eventdata->value = 0; + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + eventdata); + XtAddCallback( + mitem, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + ui_set_widget_groups(obj->ctx, mitem, it->groups); +} + +void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { + Widget s = XmCreateSeparatorGadget (p, "menuseparator", NULL, 0); + XtManageChild(s); +} + +void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuCheckItem *it = (UiMenuCheckItem*)item; + + Arg args[4]; + int n = 0; + XmString s = NULL; + if(it->label) { + s = XmStringCreateLocalized(it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + //XtSetArg(args[n], XmNvisibleWhenOff, 0); n++; + Widget checkbox = XtCreateManagedWidget( + "menutogglebutton", + xmToggleButtonWidgetClass, + p, + args, + n); + if(s) { + XmStringFree(s); + } + + ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0); + + ui_set_widget_groups(obj->ctx, checkbox, it->groups); +} + +void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuRadioItem *it = (UiMenuRadioItem*)item; + + Arg args[4]; + int n = 0; + XmString s = NULL; + if(it->label) { + s = XmStringCreateLocalized(it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++; + + Widget button = XmCreateToggleButton(p, "menuradiobutton", args, n); + XtManageChild(button); + + ui_bind_radiobutton(obj, button, NULL, it->varname, it->callback, it->userdata, 0); +} + +void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuItemList *il = (UiMenuItemList*)item; + const CxAllocator *a = obj->ctx->allocator; + + UiActiveMenuItemList *ls = cxMalloc( + a, + sizeof(UiActiveMenuItemList)); + ls->object = obj; + ls->menu = p; + ls->index = i; + ls->oldcount = 0; + ls->getvalue = il->getvalue; + ls->callback = il->callback; + ls->userdata = il->userdata; + ls->addseparator = il->addseparator; + + ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); //uic_widget_var(obj->ctx, obj->ctx, NULL, il->varname, UI_VAR_LIST); + UiList *list = ls->var->value; + + UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls); + list->observers = ui_obsvlist_add(list->observers, observer); + uic_list_register_observer_destructor(obj->ctx, list, observer); + + ui_update_menuitem_list(NULL, ls); +} + +void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) { + XmString s = NULL; + Arg args[4]; + int n; + + UiList *ls; + if(list->var && list->var->value) { + ls = list->var->value; + } else { + return; + } + + if(list->oldcount > 0) { + Widget *children; + int nc; + + XtVaGetValues( + list->menu, + XmNchildren, + &children, + XmNnumChildren, + &nc, + NULL); + + for(int i=0;ioldcount;i++) { + XtDestroyWidget(children[list->index + i]); + } + } + + void* elm = ui_list_first(ls); + int i = 0; + if(elm && list->addseparator) { + XtSetArg(args[0], XmNpositionIndex, list->index); + Widget s = XmCreateSeparatorGadget(list->menu, "menuseparator", args, 1); + XtManageChild(s); + i++; + } + + ui_getvaluefunc getvalue = list->getvalue; + int pos = list->index; + while(elm) { + n = 0; + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + if(label) { + s = XmStringCreateLocalized(label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + XtSetArg(args[n], XmNpositionIndex, pos+i); n++; + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + list->menu, + args, + n); + if(s) { + XmStringFree(s); + } + + if(list->callback) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = list->callback; + eventdata->userdata = list->userdata; + eventdata->obj = list->object; + eventdata->value = 0; + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + eventdata); + XtAddCallback( + mitem, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + elm = ui_list_next(ls); + i++; + } + + list->oldcount = i; +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/menu.h --- a/ui/motif/menu.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/menu.h Mon Jan 06 22:22:55 2025 +0100 @@ -31,12 +31,40 @@ #include "../ui/menu.h" #include "../common/menu.h" +#include "../common/context.h" #ifdef __cplusplus extern "C" { #endif +typedef struct UiActiveMenuItemList UiActiveMenuItemList; +struct UiActiveMenuItemList { + UiObject *object; + Widget menu; + int index; + int oldcount; + UiVar *var; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; + bool addseparator; +}; + +typedef void(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*); + +void ui_create_menubar(UiObject *obj, Widget window); +void ui_add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_st_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj); +void add_checkitemnv_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); + +void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list); #ifdef __cplusplus } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/text.c --- a/ui/motif/text.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/text.c Mon Jan 06 22:22:55 2025 +0100 @@ -28,7 +28,666 @@ #include #include +#include #include "text.h" #include "container.h" +#include + + + +/* ------------------------------ Text Field ------------------------------ */ + +static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs args, int frameless, int password) { + Arg xargs[16]; + int n = 0; + + if(frameless) { + XtSetArg(xargs[n], XmNshadowThickness, 0); + n++; + } + if(password) { + // TODO + } + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + char *name = args.name ? (char*)args.name : "textfield"; + Widget textfield = XmCreateTextField(parent, name, xargs, n); + XtManageChild(textfield); + + ui_set_widget_groups(obj->ctx, textfield, args.groups); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING); + if(var) { + UiString *value = (UiString*)var->value; + value->obj = textfield; + value->get = ui_textfield_get; + value->set = ui_textfield_set; + + if(value->value.ptr) { + ui_textfield_set(value, value->value.ptr); + } + } + + return textfield; +} + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) { + return create_textfield(obj, args, FALSE, FALSE); +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) { + return create_textfield(obj, args, TRUE, FALSE); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) { + return create_textfield(obj, args, FALSE, FALSE); +} + +char* ui_textfield_get(UiString *str) { + str->value.free(str->value.ptr); + char *value = XmTextFieldGetString(str->obj); + str->value.ptr = value; + str->value.free = (ui_freefunc)XtFree; + return value; +} + +void ui_textfield_set(UiString *str, const char *value) { + XmTextFieldSetString(str->obj, (void*)value); + str->value.ptr = NULL; + str->value.free(str->value.ptr); +} + + + + + +/* -------------------- path bar -------------------- */ + +#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) +#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) +#define XNETextGetString(widget) XmTextFieldGetString(widget) +#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) +#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) +#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) + +typedef void(*updatedir_callback)(void*,char*,int); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget focus_widget; + + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + int shift; + + UiPathElm *current_pathelms; + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + int focus; + + updatedir_callback updateDir; + void *updateDirData; + + ui_pathelm_func getpathelm; + void *getpathelmdata; +} PathBar; + +void PathBarSetPath(PathBar *bar, const char *path); + +void pathbar_resize(Widget w, PathBar *p, XtPointer d) +{ + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;inumSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + 10 > width) { + arrows = True; + pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x = p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;inumSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + + XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); +} + +static void pathbarActivateTF(PathBar *p) +{ + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); +} + +void PathBarActivateTextfield(PathBar *p) +{ + p->focus = 1; + pathbarActivateTF(p); +} + +void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + p->focus = 0; + pathbarActivateTF(p); + } + } +} + +void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + if(--p->focus < 0) { + p->input = False; + XtUnmanageChild(p->textfield); + } +} + +static cxmutstr concat_path_s(cxstring base, cxstring path) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == '/') { + if(path.ptr[0] == '/') { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != '/') { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, CX_STR("/"), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +static char* ConcatPath(const char *path1, const char *path2) { + return concat_path_s(cx_str(path1), cx_str(path2)).ptr; +} + +void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) +{ + char *newpath = XNETextGetString(p->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *home = getenv("HOME"); + char *cp = ConcatPath(home, p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char curdir[2048]; + curdir[0] = 0; + getcwd(curdir, 2048); + char *cp = ConcatPath(curdir, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + PathBarSetPath(p, newpath); + if(p->updateDir) { + p->updateDir(p->updateDirData, newpath, -1); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); + + if(p->focus_widget) { + XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); + } + } +} + +void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) +{ + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) +{ + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } else if(event->xkey.keycode == 36) { + pathbar_pathinput(pb->textfield, pb, NULL); + *dispatch = False; + } + } +} + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + + bar->focus_widget = NULL; + + bar->getpathelm = NULL; + bar->getpathelmdata = NULL; + bar->current_pathelms = NULL; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + XtManageChild(bar->left); + XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) +{ + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + int i; + for(i=0;inumSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + UiPathElm elm = bar->current_pathelms[i]; + cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len)); + if(bar->updateDir) { + bar->updateDir(bar->updateDirData, name.ptr, i); + } + free(name.ptr); +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ipath) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;inumSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + XtManageChild(bar->left); + XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); + size_t nelm = 0; + UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); + if (!path_elm) { + return; + } + bar->current_pathelms = path_elm; + bar->numSegments = nelm; + bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); + + for(int i=0;iwidget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + bar->pathSegments[i] = button; + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XNETextSetString(bar->textfield, (char*)path); + XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarDestroy(PathBar *pathbar) { + if(pathbar->path) { + XtFree(pathbar->path); + } + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} + + +/* ---------------------------- Path Text Field ---------------------------- */ + +static void destroy_pathbar(Widget w, XtPointer *data, XtPointer d) { + PathBar *pathbar = (PathBar*)data; + // TODO: check if there is somonething missing + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} + +// TODO: move to common +static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { + cxstring *pathelms; + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + + if (nelm == 0) { + *ret_nelm = 0; + return NULL; + } + + UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm)); + size_t n = nelm; + int j = 0; + for (int i = 0; i < nelm; i++) { + cxstring c = pathelms[i]; + if (c.length == 0) { + if (i == 0) { + c.length = 1; + } + else { + n--; + continue; + } + } + + cxmutstr m = cx_strdup(c); + elms[j].name = m.ptr; + elms[j].name_len = m.length; + + size_t elm_path_len = c.ptr + c.length - full_path; + cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len)); + elms[j].path = elm_path.ptr; + elms[j].path_len = elm_path.length; + + j++; + } + *ret_nelm = n; + + return elms; +} + +static void pathbar_activate(void *data, char *path, int index) { + UiEventData *event = data; + UiEvent evt; + evt.obj = event->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = path; + evt.intval = index; + event->callback(&evt, event->userdata); +} + +UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + // TODO: name + + + PathBar *pathbar = CreatePathBar(parent, xargs, n); + if(!args.getpathelm) { + pathbar->getpathelm= default_pathelm_func; + } else { + pathbar->getpathelm = args.getpathelm; + pathbar->getpathelmdata = args.getpathelmdata; + } + + + XtManageChild(pathbar->widget); + ctn->add(ctn, pathbar->widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING); + if (var) { + UiString* value = (UiString*)var->value; + value->obj = pathbar; + value->get = ui_path_textfield_get; + value->set = ui_path_textfield_set; + + if(value->value.ptr) { + char *str = strdup(value->value.ptr); + ui_string_set(value, str); + free(str); + } + } + + if(args.onactivate) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = args.onactivate; + eventdata->userdata = args.onactivatedata; + eventdata->obj = obj; + eventdata->value = 0; + + pathbar->updateDir = pathbar_activate; + pathbar->updateDirData = eventdata; + + XtAddCallback( + pathbar->widget, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + XtAddCallback( + pathbar->widget, + XmNdestroyCallback, + (XtCallbackProc)destroy_pathbar, + pathbar); + + return pathbar->widget; +} + +char* ui_path_textfield_get(UiString *str) { + PathBar *pathbar = str->obj; + str->value.free(str->value.ptr); + char *value = XmTextFieldGetString(pathbar->textfield); + str->value.ptr = value; + str->value.free = (ui_freefunc)XtFree; + return value; +} + +void ui_path_textfield_set(UiString *str, const char *value) { + PathBarSetPath(str->obj, value); +} diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/text.h --- a/ui/motif/text.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/text.h Mon Jan 06 22:22:55 2025 +0100 @@ -37,6 +37,13 @@ extern "C" { #endif +char* ui_textfield_get(UiString *str); +void ui_textfield_set(UiString *str, const char *value); + +char* ui_path_textfield_get(UiString *str); +void ui_path_textfield_set(UiString *str, const char *value); + + #ifdef __cplusplus } #endif diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/toolkit.c --- a/ui/motif/toolkit.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/toolkit.c Mon Jan 06 22:22:55 2025 +0100 @@ -65,16 +65,15 @@ static String fallback[] = { - //"*fontList: -dt-interface system-medium-r-normal-s*utf*:", - "*text_area*renderTable: f1", - "*f1*fontType: FONT_IS_XFT", - "*f1*fontName: Monospace", - "*f1*fontSize: 11", + //"*fontList: -dt-interface system-medium-r-normal-s*utf*:", "*renderTable: rt", "*rt*fontType: FONT_IS_XFT", "*rt*fontName: Sans", "*rt*fontSize: 11", + "*progresss_spinner*renderTable*fontType: FONT_IS_FONT", + "*progresss_spinner*renderTable*fontName: Cursor", + "*window_frame.shadowType: SHADOW_ETCHED_OUT", "*window_frame.shadowThickness: 1", "*togglebutton.shadowThickness: 1", @@ -309,7 +308,7 @@ 4); } -void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d) { +void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) { free(data); } diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/toolkit.h --- a/ui/motif/toolkit.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/toolkit.h Mon Jan 06 22:22:55 2025 +0100 @@ -43,6 +43,7 @@ ui_callback callback; void *userdata; int value; + void *customdata; } UiEventData; typedef struct UiEventDataExt { @@ -91,7 +92,7 @@ void ui_secondary_event_loop(int *loop); void ui_window_dark_theme(Display *dp, Window window); -void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d); +void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d); void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ; void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/motif/window.c --- a/ui/motif/window.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/motif/window.c Mon Jan 06 22:22:55 2025 +0100 @@ -97,12 +97,25 @@ toplevel, NULL); + // menu + if(!simple) { + ui_create_menubar(obj, window); + } + // content frame n = 0; Widget frame = XmCreateFrame(window, "window_frame", args, n); XtManageChild(frame); - Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, frame, NULL, 0); + Widget form = XmCreateForm(frame, "window_form", args, 0); + XtManageChild(form); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, form, args, n); UiContainerX *container = ui_box_container(obj, vbox, UI_BOX_VERTICAL); uic_object_push_container(obj, container); diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/ui/container.h --- a/ui/ui/container.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/ui/container.h Mon Jan 06 22:22:55 2025 +0100 @@ -148,6 +148,49 @@ int spacing; } UiSidebarArgs; +typedef struct UiItemListContainerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int margin; + int spacing; + + int sub_margin; + int sub_spacing; + int sub_columnspacing; + int sub_rowspacing; + + UiList *value; + const char *varname; + /* + * void create_ui(UiObject *obj, int index, void *elm, void *userdata) + * + * UI constructor for each list element + * + * This callback is executed for each list element. A new UiObject is + * created for every item. + */ + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + + /* + * ItemList container type + * Only UI_CONTAINER_VBOX or UI_CONTAINER_HBOX are supported + */ + UiSubContainerType container; + + /* + * item root container + */ + UiSubContainerType subcontainer; +} UiItemListContainerArgs; struct UiContainerX { void *container; @@ -185,6 +228,8 @@ #define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_itemlist(obj, ...) ui_itemlist_create(obj, (UiItemListContainerArgs) { __VA_ARGS__} ) + UIEXPORT void ui_end(UiObject *obj); // deprecated UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end @@ -208,6 +253,7 @@ UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args); +UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args); UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/ui/display.h --- a/ui/ui/display.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/ui/display.h Mon Jan 06 22:22:55 2025 +0100 @@ -65,6 +65,8 @@ UiBool vfill; int colspan; int rowspan; + const char *name; + const char *style_class; const char* label; UiAlignment align; @@ -82,6 +84,8 @@ int colspan; int rowspan; int width; + const char *name; + const char *style_class; double min; double max; @@ -97,6 +101,8 @@ UiBool vfill; int colspan; int rowspan; + const char *name; + const char *style_class; UiInteger* value; const char* varname; diff -r d2bd73d28ff1 -r 7b3a3130be44 ui/ui/menu.h --- a/ui/ui/menu.h Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/ui/menu.h Mon Jan 06 22:22:55 2025 +0100 @@ -64,6 +64,7 @@ ui_getvaluefunc getvalue; ui_callback onselect; void* onselectdata; + UiBool addseparator; } UiMenuItemListArgs; #define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close())