Fri, 12 Dec 2025 12:28:32 +0100
remove old UCX2 properties
| ucx/allocator.c | file | annotate | diff | comparison | revisions | |
| ucx/buffer.c | file | annotate | diff | comparison | revisions | |
| ucx/cx/allocator.h | file | annotate | diff | comparison | revisions | |
| ucx/cx/buffer.h | file | annotate | diff | comparison | revisions | |
| ucx/cx/common.h | file | annotate | diff | comparison | revisions | |
| ucx/cx/json.h | file | annotate | diff | comparison | revisions | |
| ucx/cx/properties.h | file | annotate | diff | comparison | revisions | |
| ucx/cx/tree.h | file | annotate | diff | comparison | revisions | |
| ucx/json.c | file | annotate | diff | comparison | revisions | |
| ucx/kv_list.c | file | annotate | diff | comparison | revisions | |
| ucx/properties.c | file | annotate | diff | comparison | revisions | |
| ucx/tree.c | file | annotate | diff | comparison | revisions | |
| ui/common/properties.c | file | annotate | diff | comparison | revisions | |
| ui/common/ucx_properties.c | file | annotate | diff | comparison | revisions | |
| ui/common/ucx_properties.h | file | annotate | diff | comparison | revisions |
--- a/ucx/allocator.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/allocator.c Fri Dec 12 12:28:32 2025 +0100 @@ -31,6 +31,35 @@ #include <errno.h> #include <string.h> +#ifdef _WIN32 +#include <Windows.h> +#include <sysinfoapi.h> +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = (unsigned long) sysinfo.dwPageSize; + } + return ps; +} +#else +#include <unistd.h> +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + long sc = sysconf(_SC_PAGESIZE); + if (sc < 0) { + // fallback for systems which do not report a value here + ps = 4096; // LCOV_EXCL_LINE + } else { + ps = (unsigned long) sc; + } + } + return ps; +} +#endif + static void *cx_malloc_stdlib( cx_attr_unused void *d, size_t n @@ -79,6 +108,11 @@ void **mem, size_t n ) { + if (n == 0) { + free(*mem); + *mem = NULL; + return 0; + } void *nmem = realloc(*mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -93,6 +127,11 @@ size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + free(*mem); + *mem = NULL; + return 0; + } size_t n; if (cx_szmul(nmemb, size, &n)) { errno = EOVERFLOW; @@ -156,6 +195,11 @@ void **mem, size_t n ) { + if (n == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -171,6 +215,11 @@ size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = cxReallocArray(allocator, *mem, nmemb, size); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -194,3 +243,7 @@ ) { allocator->cl->free(allocator->data, mem); } + +void cxFreeDefault(void *mem) { + cxDefaultAllocator->cl->free(cxDefaultAllocator->data, mem); +}
--- a/ucx/buffer.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/buffer.c Fri Dec 12 12:28:32 2025 +0100 @@ -32,35 +32,6 @@ #include <string.h> #include <errno.h> -#ifdef _WIN32 -#include <Windows.h> -#include <sysinfoapi.h> -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - ps = sysinfo.dwPageSize; - } - return ps; -} -#else -#include <unistd.h> -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - long sc = sysconf(_SC_PAGESIZE); - if (sc < 0) { - // fallback for systems which do not report a value here - ps = 4096; // LCOV_EXCL_LINE - } else { - ps = (unsigned long) sc; - } - } - return ps; -} -#endif - 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); @@ -95,21 +66,10 @@ buffer->bytes = space; } buffer->capacity = capacity; + buffer->max_capacity = SIZE_MAX; buffer->size = 0; buffer->pos = 0; - buffer->flush = NULL; - - return 0; -} - -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -) { - buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); - if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE - memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; } @@ -117,7 +77,6 @@ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -239,9 +198,12 @@ } int cxBufferReserve(CxBuffer *buffer, size_t newcap) { - if (newcap <= buffer->capacity) { + if (newcap == buffer->capacity) { return 0; } + if (newcap > buffer->max_capacity) { + return -1; + } 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); @@ -254,42 +216,57 @@ return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { + buffer->flags |= CX_BUFFER_FREE_CONTENTS; buffer->capacity = newcap; + if (buffer->size > newcap) { + buffer->size = newcap; + } return 0; } else { return -1; // LCOV_EXCL_LINE } } -static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { - unsigned long pagesize = system_page_size(); - // if page size is larger than 64 KB - for some reason - truncate to 64 KB - if (pagesize > 65536) pagesize = 65536; - if (mincap < pagesize) { - // when smaller as one page, map to the next power of two - mincap--; - mincap |= mincap >> 1; - mincap |= mincap >> 2; - mincap |= mincap >> 4; - // last operation only needed for pages larger 4096 bytes - // but if/else would be more expensive than just doing this - mincap |= mincap >> 8; - mincap++; - } else { - // otherwise, map to a multiple of the page size - mincap -= mincap % pagesize; - mincap += pagesize; - // note: if newcap is already page aligned, - // this gives a full additional page (which is good) +int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { + if (capacity < buffer->capacity) { + return -1; } - return mincap; + buffer->max_capacity = capacity; + return 0; } int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { if (newcap <= buffer->capacity) { return 0; } - newcap = cx_buffer_calculate_minimum_capacity(newcap); + if (newcap > buffer->max_capacity) { + return -1; + } + if (newcap < buffer->max_capacity) { + unsigned long pagesize = cx_system_page_size(); + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + if (newcap > buffer->max_capacity) { + newcap = buffer->max_capacity; + } + } return cxBufferReserve(buffer, newcap); } @@ -315,54 +292,6 @@ } } -static size_t cx_buffer_flush_helper( - const CxBuffer *buffer, - const unsigned char *src, - size_t size, - size_t nitems -) { - // 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) { - flushed_total += flushed; - src += flushed * size; - nitems -= flushed; - } else { - // if no bytes can be flushed out anymore, we give up - break; - } - } - return flushed_total; -} - -static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { - // flush the current contents of the buffer - unsigned char *space = buffer->bytes; - size_t remaining = buffer->pos / size; - size_t flushed_total = cx_buffer_flush_helper( - buffer, space, size, remaining); - - // shift the buffer left after flushing - // IMPORTANT: up to this point, copy on write must have been - // performed already, because we can't do error handling here - cxBufferShiftLeft(buffer, flushed_total*size); - - return flushed_total; -} - -size_t cxBufferFlush(CxBuffer *buffer) { - if (buffer_copy_on_write(buffer)) return 0; - return cx_buffer_flush_impl(buffer, 1); -} - size_t cxBufferWrite( const void *ptr, size_t size, @@ -380,107 +309,52 @@ return nitems; } - size_t len, total_flushed = 0; -cx_buffer_write_retry: + size_t len; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return total_flushed; + return 0; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return total_flushed; + return 0; } + const size_t required = buffer->pos + len; - size_t required = buffer->pos + len; - bool perform_flush = false; + // check if we need to auto-extend if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush != NULL) { - size_t newcap = cx_buffer_calculate_minimum_capacity(required); - if (newcap > buffer->flush->threshold) { - newcap = buffer->flush->threshold; - } - if (cxBufferReserve(buffer, newcap)) { - return total_flushed; // LCOV_EXCL_LINE - } - if (required > newcap) { - perform_flush = true; - } - } else { - if (cxBufferMinimumCapacity(buffer, required)) { - return total_flushed; // LCOV_EXCL_LINE - } - } - } else { - if (buffer->flush != NULL) { - perform_flush = true; - } else { - // truncate data, if we can neither extend nor flush - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len % size; - } - nitems = len / size; + size_t newcap = required < buffer->max_capacity + ? required : buffer->max_capacity; + if (cxBufferMinimumCapacity(buffer, newcap)) { + return 0; // LCOV_EXCL_LINE } } } + // check again and truncate data if capacity is still not enough + if (required > buffer->capacity) { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; + } + // check here and not above because of possible truncation if (len == 0) { - return total_flushed; + return 0; } // check if we need to copy if (buffer_copy_on_write(buffer)) return 0; // perform the operation - if (perform_flush) { - size_t items_flushed; - if (buffer->pos == 0) { - // if we don't have data in the buffer, but are instructed - // to flush, it means that we are supposed to relay the data - items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); - if (items_flushed == 0) { - // we needed to relay data, but could not flush anything - // i.e. we have to give up to avoid endless trying - return 0; - } - nitems -= items_flushed; - total_flushed += items_flushed; - if (nitems > 0) { - ptr = ((unsigned char*)ptr) + items_flushed * size; - goto cx_buffer_write_retry; - } - return total_flushed; - } else { - items_flushed = cx_buffer_flush_impl(buffer, size); - if (items_flushed == 0) { - // flush target is full, let's try to truncate - size_t remaining_space; - if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - remaining_space = buffer->flush->threshold > buffer->pos - ? buffer->flush->threshold - buffer->pos - : 0; - } else { - remaining_space = buffer->capacity > buffer->pos - ? buffer->capacity - buffer->pos - : 0; - } - nitems = remaining_space / size; - if (nitems == 0) { - return total_flushed; - } - } - goto cx_buffer_write_retry; - } - } else { - memcpy(buffer->bytes + buffer->pos, ptr, len); - buffer->pos += len; - if (buffer->pos > buffer->size) { - buffer->size = buffer->pos; - } - return total_flushed + nitems; + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; } + return nitems; } size_t cxBufferAppend(
--- a/ucx/cx/allocator.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/allocator.h Fri Dec 12 12:28:32 2025 +0100 @@ -146,6 +146,17 @@ const CxAllocator *allocator, void *data); /** + * Returns the system's memory page size. + * + * If the page size cannot be retrieved from the system, + * a default of 4096 bytes is assumed. + * + * @return the system's memory page size in bytes + */ +cx_attr_nodiscard +CX_EXPORT unsigned long cx_system_page_size(void); + +/** * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * @@ -428,9 +439,9 @@ */ #define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__) /** - * Convenience macro that invokes cxFree() with the cxDefaultAllocator. + * Convenience function that invokes cxFree() with the cxDefaultAllocator. */ -#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__) +CX_EXPORT void cxFreeDefault(void *mem); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/buffer.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/buffer.h Fri Dec 12 12:28:32 2025 +0100 @@ -99,59 +99,6 @@ */ #define cxBufferReadFunc ((cx_read_func) cxBufferRead) -/** - * Configuration for automatic flushing. - */ -struct cx_buffer_flush_config_s { - /** - * The buffer may not extend beyond this threshold before starting to flush. - * - * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. - * The threshold will be the maximum capacity the buffer is extended to - * before flushing. - */ - size_t threshold; - /** - * The block size for the elements to flush. - */ - size_t blksize; - /** - * The maximum number of blocks to flush in one cycle. - * - * @attention While it is guaranteed that cxBufferFlush() will not flush - * more blocks, this is not necessarily the case for cxBufferWrite(). - * After performing a flush cycle, cxBufferWrite() will retry the write - * operation and potentially trigger another flush cycle, until the - * flush target accepts no more data. - */ - size_t blkmax; - - /** - * The target for the write function. - */ - void *target; - - /** - * The write-function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func wfunc; -}; - -/** - * Type alias for the flush configuration struct. - * - * @code - * struct cx_buffer_flush_config_s { - * size_t threshold; - * size_t blksize; - * size_t blkmax; - * void *target; - * cx_write_func wfunc; - * }; - * @endcode - */ -typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; /** Structure for the UCX buffer data. */ struct cx_buffer_s { @@ -168,16 +115,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. */ size_t capacity; + /** Maximum capacity that this buffer may grow to. */ + size_t max_capacity; /** Current size of the buffer content. */ size_t size; /** @@ -231,23 +174,6 @@ const CxAllocator *allocator, int flags); /** - * Configures the buffer for flushing. - * - * Flushing can happen automatically when data is written - * to the buffer (see cxBufferWrite()) or manually when - * cxBufferFlush() is called. - * - * @param buffer the buffer - * @param config the flush configuration - * @retval zero success - * @retval non-zero failure - * @see cxBufferFlush() - * @see cxBufferWrite() - */ -cx_attr_nonnull -CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); - -/** * Destroys the buffer contents. * * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled. @@ -443,6 +369,8 @@ * Ensures that the buffer has the required capacity. * * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is larger, the buffer is shrunk and superfluous + * content is discarded. * * This function will reserve no more bytes than requested, in contrast to * cxBufferMinimumCapacity(), which may reserve more bytes to improve the @@ -450,7 +378,7 @@ * * @param buffer the buffer * @param capacity the required capacity for this buffer - * @retval zero the capacity was already sufficient or successfully increased + * @retval zero on success * @retval non-zero on allocation failure * @see cxBufferShrink() * @see cxBufferMinimumCapacity() @@ -459,6 +387,25 @@ CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); /** + * Limits the buffer's capacity. + * + * If the current capacity is already larger, this function fails and returns + * non-zero. + * + * The capacity limit will affect auto-extension features, as well as future + * calls to cxBufferMinimumCapacity() and cxBufferReserve(). + * + * @param buffer the buffer + * @param capacity the maximum allowed capacity for this buffer + * @retval zero the limit is applied + * @retval non-zero the new limit is smaller than the current capacity + * @see cxBufferReserve() + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +CX_EXPORT int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity); + +/** * Ensures that the buffer has a minimum capacity. * * If the current capacity is not sufficient, the buffer will be generously @@ -471,6 +418,7 @@ * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferMaximumCapacity() * @see cxBufferReserve() * @see cxBufferShrink() */ @@ -500,33 +448,13 @@ /** * Writes data to a CxBuffer. * - * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position, and the position of the buffer is increased - * by the number of bytes written. - * - * If flushing is enabled and the buffer needs to flush, the data is flushed to - * the target until the target signals that it cannot take more data by - * returning zero via the respective write function. In that case, the remaining - * data in this buffer is shifted to the beginning of this buffer so that the - * newly available space can be used to append as much data as possible. - * - * This function only stops writing more elements when the flush target and this - * buffer are both incapable of taking more data or all data has been written. + * If auto-extension is enabled, the buffer's capacity is automatically + * increased when it is not large enough to hold all data. + * By default, the capacity grows indefinitely, unless limited with + * cxBufferMaximumCapacity(). + * When auto-extension fails, this function writes no data and returns zero. * - * If, after flushing, the number of items that shall be written still exceeds - * the capacity or flush threshold, this function tries to write all items directly - * to the flush target, if possible. - * - * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer. - * That means it does @em not include the number of items that were already in - * the buffer and were also flushed during the process. - * - * @attention - * When @p size is larger than one and the contents of the buffer are not aligned - * with @p size, flushing stops after all complete items have been flushed, leaving - * the misaligned part in the buffer. - * Afterward, this function only writes as many items as possible to the buffer. + * The position of the buffer is moved alongside the written data. * * @note The signature is compatible with the fwrite() family of functions. * @@ -566,62 +494,6 @@ 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 operation 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 makes little sense to put - * readonly data into an UCX buffer for flushing instead - * of writing it directly to the target. - * - * @param buffer the buffer - * @return the number of successfully flushed bytes - * @see cxBufferEnableFlushing() - */ -cx_attr_nonnull -CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); - -/** * Reads data from a CxBuffer. * * The position of the buffer is increased by the number of bytes read. @@ -645,8 +517,9 @@ * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature - * is disabled or the buffer extension fails, @c EOF is returned. + * the buffer capacity is extended, unless a limit set by + * cxBufferMaximumCapacity() is reached. + * If the feature is disabled or the buffer extension fails, @c EOF is returned. * * On successful writing, the position of the buffer is increased. * @@ -655,8 +528,8 @@ * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has been written or @c EOF when the end of the stream is - * reached, and automatic extension is not enabled or not possible + * @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() */ cx_attr_nonnull @@ -665,7 +538,8 @@ /** * Writes a terminating zero to a buffer at the current position. * - * If successful, sets the size to the current position and advances the position by one. + * If successful, sets the size to the current position and advances + * the position by one. * * The purpose of this function is to have the written data ready to be used as * a C string with the buffer's size being the length of that string.
--- a/ucx/cx/common.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/common.h Fri Dec 12 12:28:32 2025 +0100 @@ -80,10 +80,10 @@ #define UCX_COMMON_H /** Major UCX version as integer constant. */ -#define UCX_VERSION_MAJOR 3 +#define UCX_VERSION_MAJOR 4 /** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 1 +#define UCX_VERSION_MINOR 0 /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
--- a/ucx/cx/json.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/json.h Fri Dec 12 12:28:32 2025 +0100 @@ -41,6 +41,7 @@ #include "string.h" #include "buffer.h" #include "array_list.h" +#include "map.h" #include <string.h> @@ -188,9 +189,10 @@ */ typedef struct cx_json_array_s CxJsonArray; /** - * Type alias for the JSON object struct. + * Type alias for the map representing a JSON object. + * The map contains pointers of type @c CxJsonValue. */ -typedef struct cx_json_object_s CxJsonObject; +typedef CxMap* CxJsonObject; /** * Type alias for a JSON string. */ @@ -209,46 +211,13 @@ 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; + CX_ARRAY_DECLARE(CxJsonValue*, data); }; /** @@ -295,7 +264,7 @@ * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; - } value; + }; }; /** @@ -349,11 +318,11 @@ CxJsonValue *parsed; /** - * A pointer to an intermediate state of a currently parsed object member. + * The name of a not yet completely parsed object member. * * Never access this value manually. */ - CxJsonObjValue uncompleted_member; + cxmutstr uncompleted_member_name; /** * State stack. @@ -439,10 +408,6 @@ */ bool pretty; /** - * Set false to output the members in the order in which they were added. - */ - bool sort_members; - /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. * Note that the actual number of digits may be lower, depending on the concrete number. @@ -530,8 +495,8 @@ /** * Destroys and re-initializes the JSON interface. * - * You might want to use this to reset the parser after - * encountering a syntax error. + * You must use this to reset the parser after encountering a syntax error + * if you want to continue using it. * * @param json the JSON interface */ @@ -592,6 +557,36 @@ */ #define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) + +/** + * Internal function - use cxJsonFromString() instead. + * + * @param allocator the allocator for the JSON value + * @param str the string to parse + * @param value a pointer where the JSON value shall be stored to + * @return status code + */ +cx_attr_nonnull_arg(3) +CX_EXPORT CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value); + +/** + * Parses a string into a JSON value. + * + * @param allocator (@c CxAllocator*) the allocator for the JSON value + * @param str (any string) the string to parse + * @param value (@c CxJsonValue**) a pointer where the JSON value shall be stored to + * @retval CX_JSON_NO_ERROR success + * @retval CX_JSON_NO_DATA the string was empty or blank + * @retval CX_JSON_INCOMPLETE_DATA the string unexpectedly ended + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for the 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 + */ +#define cxJsonFromString(allocator, str, value) \ + cx_json_from_string(allocator, cx_strcast(str), value) + /** * Creates a new (empty) JSON object. * @@ -1077,7 +1072,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal != CX_JSON_NULL; } /** @@ -1094,7 +1089,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_TRUE; } /** @@ -1111,7 +1106,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_FALSE; } /** @@ -1124,7 +1119,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_NULL; } /** @@ -1202,7 +1197,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { - return value->value.literal == CX_JSON_TRUE; + return value->literal == CX_JSON_TRUE; } /** @@ -1216,7 +1211,7 @@ */ cx_attr_nonnull CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { - return value->value.array.array_size; + return value->array.data_size; } /** @@ -1277,14 +1272,14 @@ */ cx_attr_nonnull CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { - return value->value.object.values_size; + return cxCollectionSize(value->object); } /** - * Returns an iterator over the JSON object members. + * Returns a map iterator over the JSON object members. * - * The iterator yields values of type @c CxJsonObjValue* which - * contain the name and value of the member. + * The iterator yields values of type @c CxMapEntry* which + * contain the name and the @c CxJsonObjValue* of the member. * * If the @p value is not a JSON object, the behavior is undefined. * @@ -1293,7 +1288,7 @@ * @see cxJsonIsObject() */ cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); +CX_EXPORT CxMapIterator cxJsonObjIter(const CxJsonValue *value); /** * Internal function, do not use.
--- a/ucx/cx/properties.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/properties.h Fri Dec 12 12:28:32 2025 +0100 @@ -41,9 +41,6 @@ #include "map.h" #include "buffer.h" -#include <stdio.h> -#include <string.h> - #ifdef __cplusplus extern "C" { #endif @@ -80,9 +77,6 @@ * The character, when appearing at the end of a line, continues that line. * This is '\' by default. */ - /** - * Reserved for future use. - */ char continuation; }; @@ -141,23 +135,16 @@ */ CX_PROPERTIES_BUFFER_ALLOC_FAILED, /** - * Initializing the properties source failed. - * - * @see cx_properties_read_init_func + * A file operation failed. + * Only for cxPropertiesLoad(). + * It is system-specific if errno is set. */ - CX_PROPERTIES_READ_INIT_FAILED, + CX_PROPERTIES_FILE_ERROR, /** - * Reading from a properties source failed. - * - * @see cx_properties_read_func + * A map operation failed. + * Only for cxPropertiesLoad(). */ - CX_PROPERTIES_READ_FAILED, - /** - * Sinking a k/v-pair failed. - * - * @see cx_properties_sink_func - */ - CX_PROPERTIES_SINK_FAILED, + CX_PROPERTIES_MAP_ERROR, }; /** @@ -190,134 +177,6 @@ */ 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 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 - */ -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 - */ -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 - */ -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 - */ -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. * @@ -465,100 +324,83 @@ CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** - * Creates a properties sink for an UCX map. - * - * The values stored in the map will be pointers to freshly allocated, - * zero-terminated C strings (@c char*), which means the @p map should have been - * created with #CX_STORE_POINTERS. - * - * The cxDefaultAllocator will be used unless you specify a custom - * allocator in the optional @c data field of the returned sink. - * - * @param map the map that shall consume the k/v-pairs. - * @return the sink - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will reserve with `cxPropertiesUseStack()`. */ -cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); +CX_EXPORT extern const unsigned cx_properties_load_buf_size; /** - * Creates a properties source based on an UCX string. - * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will use to read contents from the file. */ -cx_attr_nodiscard -CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); - -/** - * Creates a properties source based on C string with the specified length. - * - * @param str the string - * @param len the length - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) -CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +CX_EXPORT extern const unsigned cx_properties_load_fill_size; /** - * Creates a properties source based on a C string. - * - * The length will be determined with strlen(), so the string MUST be - * zero-terminated. + * Internal function - use cxPropertiesLoad() instead. * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * @param config the parser config + * @param allocator the allocator for the values + * @param filename the file name + * @param target the target map + * @return status code */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); +cx_attr_nonnull_arg(4) +CX_EXPORT CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target); /** - * Creates a properties source based on an FILE. - * - * @param file the file - * @param chunk_size how many bytes may be read in one operation + * Loads properties from a file and inserts them into a map. * - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) -CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); - - -/** - * Loads properties data from a source and transfers it to a sink. + * Entries are added to the map, possibly overwriting existing entries. + * + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * This function tries to read as much data from the source as possible. - * When the source was completely consumed and at least on k/v-pair was found, - * the return value will be #CX_PROPERTIES_NO_ERROR. - * When the source was consumed but no k/v-pairs were found, the return value - * will be #CX_PROPERTIES_NO_DATA. - * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA - * is returned. In that case you should call this function again with the same - * sink and either an updated source or the same source if the source is able to - * yield the missing data. + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. * - * The other result codes apply, according to their description. - * - * @param prop the properties interface - * @param sink the sink - * @param source the source - * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found - * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed - * @retval CX_PROPERTIES_READ_FAILED reading from the source failed - * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed - * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs - * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data + * @param config the parser config + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @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 + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoadDefault() */ -cx_attr_nonnull -CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, - CxPropertiesSink sink, CxPropertiesSource source); +#define cxPropertiesLoad(config, allocator, filename, target) \ + cx_properties_load(config, allocator, cx_strcast(filename), target) + +/** + * Loads properties from a file and inserts them into a map with a default config. + * + * Entries are added to the map, possibly overwriting existing entries. + * + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. + * + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. + * + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file + * @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 + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoad() + */ +#define cxPropertiesLoadDefault(allocator, filename, target) \ + cx_properties_load(cx_properties_config_default, allocator, cx_strcast(filename), target) + #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/tree.h Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/cx/tree.h Fri Dec 12 12:28:32 2025 +0100 @@ -643,17 +643,13 @@ * Structure for holding the base data of a tree. */ struct cx_tree_s { + CX_COLLECTION_BASE; /** * The tree class definition. */ const cx_tree_class *cl; /** - * Allocator to allocate new nodes. - */ - const CxAllocator *allocator; - - /** * A pointer to the root node. * * Will be @c NULL when @c size is 0. @@ -671,21 +667,6 @@ cx_tree_node_create_func node_create; /** - * An optional simple destructor for the tree nodes. - */ - cx_destructor_func simple_destructor; - - /** - * An optional advanced destructor for the tree nodes. - */ - cx_destructor_func2 advanced_destructor; - - /** - * The pointer to additional data that is passed to the advanced destructor. - */ - void *destructor_data; - - /** * A function to compare two nodes. */ cx_tree_search_func search; @@ -696,11 +677,6 @@ cx_tree_search_data_func search_data; /** - * The number of currently stored elements. - */ - size_t size; - - /** * Offset in the node struct for the parent pointer. */ ptrdiff_t loc_parent;
--- a/ucx/json.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/json.c Fri Dec 12 12:28:32 2025 +0100 @@ -27,6 +27,7 @@ */ #include "cx/json.h" +#include "cx/kv_list.h" #include <string.h> #include <assert.h> @@ -41,90 +42,10 @@ 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 size_t 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); - return cx_array_binary_search( - obj->value.object.values, - obj->value.object.values_size, - sizeof(CxJsonObjValue), - &kv_dummy, - json_cmp_objvalue - ); -} - -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; // LCOV_EXCL_LINE - } - } - - // 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); + token->allocated = false; } } @@ -307,7 +228,9 @@ } } - if (ttype != CX_JSON_NO_TOKEN) { + if (ttype == CX_JSON_NO_TOKEN) { + return CX_JSON_NO_DATA; + } else { // uncompleted token size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { @@ -334,9 +257,8 @@ } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted_len; + return CX_JSON_INCOMPLETE_DATA; } - - return CX_JSON_INCOMPLETE_DATA; } // converts a Unicode codepoint to utf8 @@ -473,7 +395,7 @@ return result; } -static cxmutstr escape_string(cxmutstr str, bool escape_slash) { +static cxmutstr escape_string(cxstring str, bool escape_slash) { // note: this function produces the string without enclosing quotes // the reason is that we don't want to allocate memory just for that CxBuffer buf = {0}; @@ -519,11 +441,27 @@ cxBufferPut(&buf, c); } } - if (!all_printable) { - str = cx_mutstrn(buf.space, buf.size); + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); } cxBufferDestroy(&buf); - return str; + return ret; +} + +static CxJsonObject json_create_object_map(const CxAllocator *allocator) { + // TODO: we might want to add a comparator that is sorting the elements by their key + CxMap *map = cxKvListCreateAsMap(allocator, NULL, CX_STORE_POINTERS); + if (map == NULL) return NULL; // LCOV_EXCL_LINE + cxDefineDestructor(map, cxJsonValueFree); + return map; +} + +static void json_free_object_map(CxJsonObject obj) { + cxMapFree(obj); } static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { @@ -534,14 +472,11 @@ 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 + cx_array_initialize_a(json->allocator, v->array.data, 16); + if (v->array.data == 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 + v->object = json_create_object_map(json->allocator); + if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } // add the new value to a possible parent @@ -550,17 +485,17 @@ 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)) { + if (cx_array_simple_add_a(&value_realloc, parent->array.data, 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)) { + // store the pointer of the uncompleted value in the map + assert(json->uncompleted_member_name.ptr != NULL); + if (cxMapPut(parent->object, json->uncompleted_member_name, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } - json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } else { assert(false); // LCOV_EXCL_LINE } @@ -624,10 +559,8 @@ } 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}; - } + token_destroy(&json->uncompleted); + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } void cxJsonReset(CxJson *json) { @@ -720,7 +653,7 @@ if (str.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - vbuf->value.string = str; + vbuf->string = str; return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_INTEGER: @@ -730,11 +663,11 @@ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (type == CX_JSON_INTEGER) { - if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + if (cx_strtoi64(token.content, &vbuf->integer, 10)) { return_rec(CX_JSON_FORMAT_ERROR_NUMBER); } } else { - if (cx_strtod(token.content, &vbuf->value.number)) { + if (cx_strtod(token.content, &vbuf->number)) { // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE } @@ -746,11 +679,11 @@ 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; + vbuf->literal = CX_JSON_TRUE; } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { - vbuf->value.literal = CX_JSON_FALSE; + vbuf->literal = CX_JSON_FALSE; } else { - vbuf->value.literal = CX_JSON_NULL; + vbuf->literal = CX_JSON_NULL; } return_rec(CX_JSON_NO_ERROR); } @@ -786,8 +719,8 @@ 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->uncompleted_member_name.ptr == NULL); + json->uncompleted_member_name = name; assert(json->vbuf_size > 0); // next state @@ -860,29 +793,54 @@ return result; } +CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value) { + *value = &cx_json_value_nothing; + CxJson parser; + cxJsonInit(&parser, allocator); + if (cxJsonFill(&parser, str)) { + // LCOV_EXCL_START + cxJsonDestroy(&parser); + return CX_JSON_BUFFER_ALLOC_FAILED; + // LCOV_EXCL_STOP + } + CxJsonStatus status = cxJsonNext(&parser, value); + // check if we consume the total string + CxJsonValue *chk_value = NULL; + CxJsonStatus chk_status = CX_JSON_NO_DATA; + if (status == CX_JSON_NO_ERROR) { + chk_status = cxJsonNext(&parser, &chk_value); + } + cxJsonDestroy(&parser); + if (chk_status == CX_JSON_NO_DATA) { + return status; + } else { + cxJsonValueFree(*value); + // if chk_value is nothing, the free is harmless + cxJsonValueFree(chk_value); + *value = &cx_json_value_nothing; + return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; + } + +} + 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); + json_free_object_map(value->object); break; } case CX_JSON_ARRAY: { - CxJsonArray array = value->value.array; - for (size_t i = 0; i < array.array_size; i++) { - cxJsonValueFree(array.array[i]); + CxJsonArray array = value->array; + for (size_t i = 0; i < array.data_size; i++) { + cxJsonValueFree(array.data[i]); } - cxFree(value->allocator, array.array); + cxFree(value->allocator, array.data); break; } case CX_JSON_STRING: { - cxFree(value->allocator, value->value.string.ptr); + cxFree(value->allocator, value->string.ptr); break; } default: { @@ -898,15 +856,8 @@ 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); + v->object = json_create_object_map(allocator); + if (v->object == NULL) { // LCOV_EXCL_START cxFree(allocator, v); return NULL; // LCOV_EXCL_STOP @@ -920,8 +871,8 @@ 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; } + cx_array_initialize_a(allocator, v->array.data, 16); + if (v->array.data == NULL) { cxFree(allocator, v); return NULL; } return v; } @@ -931,7 +882,7 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_NUMBER; - v->value.number = num; + v->number = num; return v; } @@ -941,7 +892,7 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_INTEGER; - v->value.integer = num; + v->integer = num; return v; } @@ -953,7 +904,7 @@ 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; + v->string = s; return v; } @@ -963,7 +914,7 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_LITERAL; - v->value.literal = lit; + v->literal = lit; return v; } @@ -1042,24 +993,14 @@ 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, + arr->array.data, + arr->array.data_size, val, count ); } int cx_json_obj_put(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)) { - // LCOV_EXCL_START - cx_strfree_a(obj->allocator, &k); - return 1; - // LCOV_EXCL_STOP - } else { - return 0; - } + return cxMapPut(obj->object, name, child); } CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { @@ -1105,98 +1046,84 @@ } CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return &cx_json_value_nothing; } - return value->value.array.array[index]; + return value->array.data[index]; } CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return NULL; } - CxJsonValue *ret = value->value.array.array[index]; + CxJsonValue *ret = value->array.data[index]; // TODO: replace with a low level cx_array_remove() - size_t count = value->value.array.array_size - index - 1; + size_t count = value->array.data_size - index - 1; if (count > 0) { - memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + memmove(value->array.data + index, value->array.data + index + 1, count * sizeof(CxJsonValue*)); } - value->value.array.array_size--; + value->array.data_size--; return ret; } char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; + return value->string.ptr; } cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); + return cx_strcast(value->string); } cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; + return value->string; } double cxJsonAsDouble(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; + return (double) value->integer; } else { - return value->value.number; + return value->number; } } int64_t cxJsonAsInteger(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return value->value.integer; + return value->integer; } else { - return (int64_t) value->value.number; + return (int64_t) value->number; } } CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( - value->value.array.array, - value->value.array.array_size, + value->array.data, + value->array.data_size, true // arrays need to keep order ); } -CxIterator cxJsonObjIter(const CxJsonValue *value) { - return cxIterator( - value->value.object.values, - sizeof(CxJsonObjValue), - value->value.object.values_size, - true // TODO: objects do not always need to keep order - ); +CxMapIterator cxJsonObjIter(const CxJsonValue *value) { + return cxMapIterator(value->object); } CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { + CxJsonValue *v = cxMapGet(value->object, name); + if (v == NULL) { return &cx_json_value_nothing; } else { - return value->value.object.values[index].value; + return v; } } CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { - return NULL; - } else { - CxJsonObjValue kv = value->value.object.values[index]; - cx_strfree_a(value->allocator, &kv.name); - // TODO: replace with cx_array_remove() / cx_array_remove_fast() - value->value.object.values_size--; - memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); - return kv.value; - } + CxJsonValue *v = NULL; + cxMapRemoveAndGet(value->object, name, &v); + return v; } CxJsonWriter cxJsonWriterCompact(void) { return (CxJsonWriter) { false, - true, 6, false, 4, @@ -1207,7 +1134,6 @@ CxJsonWriter cxJsonWriterPretty(bool use_spaces) { return (CxJsonWriter) { true, - true, 6, use_spaces, 4, @@ -1272,14 +1198,8 @@ 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]; - + CxMapIterator member_iter = cxJsonObjIter(value); + cx_foreach(const CxMapEntry *, member, member_iter) { // possible indentation if (settings->pretty) { if (cx_json_writer_indent(target, wfunc, settings, depth)) { @@ -1289,26 +1209,27 @@ // the name actual += wfunc("\"", 1, 1, target); - cxmutstr name = escape_string(member->name, settings->escape_slash); + cxstring key = cx_strn(member->key->data, member->key->len); + cxmutstr name = escape_string(key, settings->escape_slash); actual += wfunc(name.ptr, 1, name.length, target); - if (name.ptr != member->name.ptr) { - cx_strfree(&name); - } actual += wfunc("\"", 1, 1, target); const char *obj_name_sep = ": "; if (settings->pretty) { actual += wfunc(obj_name_sep, 1, 2, target); - expected += 4 + member->name.length; + expected += 4 + name.length; } else { actual += wfunc(obj_name_sep, 1, 1, target); - expected += 3 + member->name.length; + expected += 3 + name.length; + } + if (name.ptr != key.ptr) { + cx_strfree(&name); } // 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) { + if (member_iter.index < member_iter.elem_count - 1) { const char *obj_value_sep = ",\n"; if (settings->pretty) { actual += wfunc(obj_value_sep, 1, 2, target); @@ -1361,13 +1282,14 @@ } case CX_JSON_STRING: { actual += wfunc("\"", 1, 1, target); - cxmutstr str = escape_string(value->value.string, settings->escape_slash); + cxmutstr str = escape_string(cx_strcast(value->string), + settings->escape_slash); actual += wfunc(str.ptr, 1, str.length, target); - if (str.ptr != value->value.string.ptr) { + actual += wfunc("\"", 1, 1, target); + expected += 2 + str.length; + if (str.ptr != value->string.ptr) { cx_strfree(&str); } - actual += wfunc("\"", 1, 1, target); - expected += 2 + value->value.string.length; break; } case CX_JSON_NUMBER: { @@ -1375,7 +1297,7 @@ // because of the way how %g is defined, we need to // double the precision and truncate ourselves precision = 1 + (precision > 15 ? 30 : 2 * precision); - snprintf(numbuf, 40, "%.*g", precision, value->value.number); + snprintf(numbuf, 40, "%.*g", precision, value->number); char *dot, *exp; unsigned char max_digits; // find the decimal separator and hope that it's one of . or , @@ -1439,17 +1361,17 @@ break; } case CX_JSON_INTEGER: { - snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + snprintf(numbuf, 32, "%" PRIi64, 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) { + if (value->literal == CX_JSON_TRUE) { actual += wfunc("true", 1, 4, target); expected += 4; - } else if (value->value.literal == CX_JSON_FALSE) { + } else if (value->literal == CX_JSON_FALSE) { actual += wfunc("false", 1, 5, target); expected += 5; } else {
--- a/ucx/kv_list.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/kv_list.c Fri Dec 12 12:28:32 2025 +0100 @@ -285,6 +285,7 @@ static void cx_kvl_map_deallocate(struct cx_map_s *map) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); kv_list->map_methods->deallocate(map); kv_list->list_methods->deallocate(&kv_list->list.base); } @@ -296,41 +297,7 @@ kv_list->map_methods->clear(map); } -static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { - cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; - // if the hash has not yet been computed, do it now - if (key.hash == 0) { - cx_hash_murmur(&key); - } - - // reserve memory in the map first - void **map_data = kv_list->map_methods->put(map, key, NULL); - if (map_data == NULL) return NULL; // LCOV_EXCL_LINE - - // insert the data into the list (which most likely destroys the sorted property) - kv_list->list.base.collection.sorted = false; - void *node_data = kv_list->list_methods->insert_element( - &kv_list->list.base, kv_list->list.base.collection.size, - kv_list->list.base.collection.store_pointer ? &value : value); - if (node_data == NULL) { // LCOV_EXCL_START - // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); - return NULL; - } // LCOV_EXCL_STOP - - // write the node pointer to the map entry - *map_data = node_data; - - // copy the key to the node data - CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); - *key_ptr = key; - - // we must return node_data here and not map_data, - // because the node_data is the actual element of this collection - return node_data; -} - -void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { +static void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data = kv_list->map_methods->get(map, key); if (node_data == NULL) return NULL; // LCOV_EXCL_LINE @@ -338,7 +305,7 @@ return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; } -int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { +static int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data; @@ -381,6 +348,43 @@ return 0; } +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // remove any existing element first + cx_kvl_map_remove(map, key, NULL); + + // now reserve new memory in the map + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + static void *cx_kvl_iter_current_entry(const void *it) { const CxMapIterator *iter = it; return (void*)&iter->entry; @@ -455,7 +459,7 @@ return iter->elem != NULL; } -CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { +static CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { CxMapIterator iter = {0}; iter.type = type;
--- a/ucx/properties.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/properties.c Fri Dec 12 12:28:32 2025 +0100 @@ -29,12 +29,15 @@ #include "cx/properties.h" #include <assert.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> const CxPropertiesConfig cx_properties_config_default = { - '=', - '#', - '\0', - '\0', + '=', + '#', + '\0', + '\0', '\\', }; @@ -94,13 +97,30 @@ // a pointer to the buffer we want to read from CxBuffer *current_buffer = &prop->input; - + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + char continuation = prop->config.continuation; + // 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'); + while (nl.length > 0) { + // check for line continuation + char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1]; + if (previous == continuation) { + // this nl is a line continuation, check the next newline + nl = cx_strchr(cx_strsubs(nl, 1), '\n'); + } else { + break; + } + } + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line @@ -127,12 +147,7 @@ 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; @@ -145,6 +160,7 @@ size_t delimiter_index = 0; size_t comment_index = 0; bool has_comment = false; + bool has_continuation = false; size_t i = 0; char c = 0; @@ -159,6 +175,9 @@ if (delimiter_index == 0 && !has_comment) { delimiter_index = i; } + } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') { + has_continuation = true; + i++; } else if (c == '\n') { break; } @@ -223,10 +242,53 @@ k = cx_strtrim(k); val = cx_strtrim(val); if (k.length > 0) { + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size); + + if (has_continuation) { + char *ptr = (char*)val.ptr; + if (current_buffer != &prop->buffer) { + // move value to the rescue buffer + if (prop->buffer.space == NULL) { + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } + prop->buffer.size = 0; + prop->buffer.pos = 0; + if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + val.ptr = prop->buffer.space; + ptr = prop->buffer.space; + } + // value.ptr is now inside the rescue buffer and we can + // remove the continuation character from the value + bool trim = false; + size_t x = 0; + for(size_t j=0;j<val.length;j++) { + c = ptr[j]; + if (j+1 < val.length && c == '\\' && ptr[j+1] == '\n') { + // skip continuation and newline character + j++; + trim = true; // enable trim in the next line + continue; + } + if (j > x) { + if (trim) { + if (isspace((unsigned char)c)) { + continue; + } + trim = false; + } + ptr[x] = c; + } + x++; + } + val.length = x; + } *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; @@ -241,180 +303,96 @@ 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 = cxMapPut(map, key, v.ptr); - if (r != 0) cx_strfree_a(alloc, &v); - return r; -} - -CxPropertiesSink cxPropertiesMapSink(CxMap *map) { - CxPropertiesSink sink; - sink.sink = map; - sink.data = (void*) cxDefaultAllocator; - sink.sink_func = cx_properties_sink_map; - return sink; -} +#ifndef CX_PROPERTIES_LOAD_FILL_SIZE +#define CX_PROPERTIES_LOAD_FILL_SIZE 1024 +#endif +const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE; +#ifndef CX_PROPERTIES_LOAD_BUF_SIZE +#define CX_PROPERTIES_LOAD_BUF_SIZE 256 +#endif +const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE; -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; +CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; } - return 0; -} - -static int cx_properties_read_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - target->ptr = src->data_ptr; - target->length = fread(src->data_ptr, 1, src->data_size, src->src); - return ferror((FILE*)src->src); -} - -static int cx_properties_read_init_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - src->data_ptr = cxMallocDefault(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 -) { - cxFreeDefault(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; -} + // sanity check for the map + const bool use_cstring = cxCollectionStoresPointers(target); + if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) { + return CX_PROPERTIES_MAP_ERROR; + } -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; -} + // create a duplicate to guarantee zero-termination + cxmutstr fname = cx_strdup(filename); + if (fname.ptr == NULL) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } -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; // LCOV_EXCL_LINE - } + // open the file + FILE *f = fopen(fname.ptr, "r"); + if (f == NULL) { + cx_strfree(&fname); + return CX_PROPERTIES_FILE_ERROR; } - // transfer the data from the source to the sink + // initialize the parser + char linebuf[cx_properties_load_buf_size]; + char fillbuf[cx_properties_load_fill_size]; CxPropertiesStatus status; - CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; - bool found = false; - while (true) { - // read input - cxstring input; - if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START - status = CX_PROPERTIES_READ_FAILED; - break; - } // LCOV_EXCL_STOP + CxProperties parser; + cxPropertiesInit(&parser, config); + cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size); - // no more data - break - if (input.length == 0) { - if (found) { - // something was found, check the last kv_status - if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { - status = CX_PROPERTIES_INCOMPLETE_DATA; - } else { - status = CX_PROPERTIES_NO_ERROR; - } - } else { - // nothing found - status = CX_PROPERTIES_NO_DATA; - } + // read/fill/parse loop + status = CX_PROPERTIES_NO_DATA; + size_t keys_found = 0; + while (true) { + size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f); + if (ferror(f)) { + status = CX_PROPERTIES_FILE_ERROR; + break; + } + if (r == 0) { break; } - - // set the input buffer and read the k/v-pairs - cxPropertiesFill(prop, input); - - do { - cxstring key, value; - kv_status = cxPropertiesNext(prop, &key, &value); - if (kv_status == CX_PROPERTIES_NO_ERROR) { - found = true; - if (sink.sink_func(prop, &sink, key, value)) { - kv_status = CX_PROPERTIES_SINK_FAILED; // LCOV_EXCL_LINE + if (cxPropertiesFilln(&parser, fillbuf, r)) { + status = CX_PROPERTIES_BUFFER_ALLOC_FAILED; + break; + } + cxstring key, value; + while (true) { + status = cxPropertiesNext(&parser, &key, &value); + if (status != CX_PROPERTIES_NO_ERROR) { + break; + } else { + cxmutstr v = cx_strdup_a(allocator, value); + if (v.ptr == NULL) { + status = CX_PROPERTIES_MAP_ERROR; + break; } + void *mv = use_cstring ? (void*)v.ptr : &v; + if (cxMapPut(target, key, mv)) { + cx_strfree(&v); + status = CX_PROPERTIES_MAP_ERROR; + break; + } + keys_found++; } - } while (kv_status == CX_PROPERTIES_NO_ERROR); - - if (kv_status > CX_PROPERTIES_OK) { - status = kv_status; + } + if (status > CX_PROPERTIES_OK) { break; } } - if (source.read_clean_func != NULL) { - source.read_clean_func(prop, &source); + // cleanup and exit + fclose(f); + cxPropertiesDestroy(&parser); + cx_strfree(&fname); + if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) { + return CX_PROPERTIES_NO_ERROR; + } else { + return status; } - - return status; }
--- a/ucx/tree.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ucx/tree.c Fri Dec 12 12:28:32 2025 +0100 @@ -734,15 +734,15 @@ if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); tree->root = node; - tree->size = 1; + tree->collection.size = 1; return 0; } int result = cx_tree_add(data, tree->search, tree->node_create, tree, &node, tree->root, cx_tree_node_layout(tree)); if (0 == result) { - tree->size++; + tree->collection.size++; } else { - cxFree(tree->allocator, node); + cxFree(tree->collection.allocator, node); } return result; } @@ -767,9 +767,9 @@ void *failed; ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, tree, &failed, tree->root, cx_tree_node_layout(tree)); - tree->size += ins; + tree->collection.size += ins; if (ins < n) { - cxFree(tree->allocator, failed); + cxFree(tree->collection.allocator, failed); } return ins; } @@ -818,24 +818,21 @@ assert(search_func != NULL); assert(search_data_func != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; - tree->allocator = allocator; + tree->collection.allocator = allocator; tree->node_create = create_func; tree->search = search_func; tree->search_data = search_data_func; - tree->simple_destructor = NULL; - tree->advanced_destructor = (cx_destructor_func2) cxFree; - tree->destructor_data = (void *) allocator; + tree->collection.advanced_destructor = (cx_destructor_func2) cxFree; + tree->collection.destructor_data = (void *) allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; - tree->root = NULL; - tree->size = 0; return tree; } @@ -845,7 +842,7 @@ if (tree->root != NULL) { cxTreeClear(tree); } - cxFree(tree->allocator, tree); + cxFree(tree->collection.allocator, tree); } CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, @@ -856,39 +853,33 @@ } assert(root != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; // set the allocator anyway, just in case... - tree->allocator = allocator; - tree->node_create = NULL; - tree->search = NULL; - tree->search_data = NULL; - tree->simple_destructor = NULL; - tree->advanced_destructor = NULL; - tree->destructor_data = NULL; + tree->collection.allocator = allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; tree->root = root; - tree->size = cxTreeSubtreeSize(tree, root); + tree->collection.size = cxTreeSubtreeSize(tree, root); return tree; } void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { - tree->size++; + tree->collection.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++; + tree->collection.size++; } int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { @@ -896,7 +887,7 @@ if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); cx_tree_link(parent, node, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; return 0; } @@ -948,7 +939,7 @@ } size_t cxTreeSize(CxTree *tree) { - return tree->size; + return tree->collection.size; } size_t cxTreeDepth(CxTree *tree) { @@ -1002,7 +993,7 @@ if (loc_last_child >= 0) tree_last_child(node) = NULL; // the tree now has one member less - tree->size--; + tree->collection.size--; return 0; } @@ -1010,12 +1001,12 @@ void cxTreeRemoveSubtree(CxTree *tree, void *node) { if (node == tree->root) { tree->root = NULL; - tree->size = 0; + tree->collection.size = 0; return; } size_t subtree_size = cxTreeSubtreeSize(tree, node); cx_tree_unlink(node, cx_tree_node_layout(tree)); - tree->size -= subtree_size; + tree->collection.size -= subtree_size; } int cxTreeDestroyNode( @@ -1025,12 +1016,7 @@ ) { 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); - } + cx_invoke_destructor(tree, node); return 0; } else { return result; @@ -1045,15 +1031,10 @@ ); 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); - } + cx_invoke_destructor(tree, child); } } - tree->size -= iter.counter; + tree->collection.size -= iter.counter; if (node == tree->root) { tree->root = NULL; }
--- a/ui/common/properties.c Fri Dec 12 12:00:34 2025 +0100 +++ b/ui/common/properties.c Fri Dec 12 12:28:32 2025 +0100 @@ -44,7 +44,7 @@ #include <cx/buffer.h> #include <cx/hash_map.h> -#include "ucx_properties.h" +#include <cx/properties.h> static CxMap *application_properties; static CxMap *language; @@ -145,6 +145,25 @@ #endif } +static int load_properties(FILE *file, CxMap *map) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + char buf[8192]; + size_t r; + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + while((r = fread(buf, 1, 8192, file)) > 0) { + cxPropertiesFilln(&prop, buf, r); + cxstring key; + cxstring value; + status = cxPropertiesNext(&prop, &key, &value); + if(status > CX_PROPERTIES_OK) { + break; + } + cxMapPut(map, key, cx_strdup(value).ptr); + } + return status == CX_PROPERTIES_NO_ERROR ? 0 : 1; +} + void uic_load_app_properties() { application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); application_properties->collection.simple_destructor = free; @@ -206,8 +225,8 @@ return; } - if(ucx_properties_load(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot load application properties.\n"); + if(load_properties(file, application_properties)) { + fprintf(stderr, "Error: Cannot load application properties.\n"); } fclose(file); @@ -228,11 +247,13 @@ } int ret = 0; - if(ucx_properties_store(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot store application properties.\n"); - ret = 1; + CxMapIterator i = cxMapIterator(application_properties); + cx_foreach(CxMapEntry *, entry, i) { + fprintf(file, "%.*s: %s\n", (int)entry->key->len, entry->key->data, entry->value); } + cxMapRehash(application_properties); + fclose(file); free(path); @@ -360,8 +381,8 @@ return 1; } - if(ucx_properties_load(lang, file)) { - fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path); + if(load_properties(file, lang)) { + fprintf(stderr, "Error: Cannot parse language file: %s.\n", path); } fclose(file);
--- a/ui/common/ucx_properties.c Fri Dec 12 12:00:34 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "ucx_properties.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -UcxProperties *ucx_properties_new() { - UcxProperties *parser = (UcxProperties*)malloc( - sizeof(UcxProperties)); - if(!parser) { - return NULL; - } - - parser->buffer = NULL; - parser->buflen = 0; - parser->pos = 0; - parser->tmp = NULL; - parser->tmplen = 0; - parser->tmpcap = 0; - parser->error = 0; - parser->delimiter = '='; - parser->comment1 = '#'; - parser->comment2 = 0; - parser->comment3 = 0; - - return parser; -} - -void ucx_properties_free(UcxProperties *parser) { - if(parser->tmp) { - free(parser->tmp); - } - free(parser); -} - -void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) { - parser->buffer = buf; - parser->buflen = len; - parser->pos = 0; -} - -static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) { - if(parser->tmpcap - parser->tmplen < len) { - size_t newcap = parser->tmpcap + len + 64; - parser->tmp = (char*)realloc(parser->tmp, newcap); - parser->tmpcap = newcap; - } - memcpy(parser->tmp + parser->tmplen, buf, len); - parser->tmplen += len; -} - -int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value) { - if(parser->tmplen > 0) { - char *buf = parser->buffer + parser->pos; - size_t len = parser->buflen - parser->pos; - cxstring str = cx_strn(buf, len); - cxstring nl = cx_strchr(str, '\n'); - if(nl.ptr) { - size_t newlen = (size_t)(nl.ptr - buf) + 1; - parser_tmp_append(parser, buf, newlen); - // the tmp buffer contains exactly one line now - - char *orig_buf = parser->buffer; - size_t orig_len = parser->buflen; - - parser->buffer = parser->tmp; - parser->buflen = parser->tmplen; - parser->pos = 0; - parser->tmp = NULL; - parser->tmpcap = 0; - parser->tmplen = 0; - // run ucx_properties_next with the tmp buffer as main buffer - int ret = ucx_properties_next(parser, name, value); - - // restore original buffer - parser->tmp = parser->buffer; - parser->buffer = orig_buf; - parser->buflen = orig_len; - parser->pos = newlen; - - /* - * if ret == 0 the tmp buffer contained just space or a comment - * we parse again with the original buffer to get a name/value - * or a new tmp buffer - */ - return ret ? ret : ucx_properties_next(parser, name, value); - } else { - parser_tmp_append(parser, buf, len); - return 0; - } - } else if(parser->tmp) { - free(parser->tmp); - parser->tmp = NULL; - } - - char comment1 = parser->comment1; - char comment2 = parser->comment2; - char comment3 = parser->comment3; - char delimiter = parser->delimiter; - - // get one line and parse it - while(parser->pos < parser->buflen) { - char *buf = parser->buffer + parser->pos; - size_t len = parser->buflen - parser->pos; - - /* - * First we check if we have at least one line. We also get indices of - * delimiter and comment chars - */ - size_t delimiter_index = 0; - size_t comment_index = 0; - int has_comment = 0; - - size_t i = 0; - char c = 0; - for(;i<len;i++) { - c = buf[i]; - if(c == comment1 || c == comment2 || c == comment3) { - if(comment_index == 0) { - comment_index = i; - has_comment = 1; - } - } else if(c == delimiter) { - if(delimiter_index == 0 && !has_comment) { - delimiter_index = i; - } - } else if(c == '\n') { - break; - } - } - - if(c != '\n') { - // we don't have enough data for a line - // store remaining bytes in temporary buffer for next round - parser->tmpcap = len + 128; - parser->tmp = (char*)malloc(parser->tmpcap); - parser->tmplen = len; - memcpy(parser->tmp, buf, len); - return 0; - } - - cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i); - // check line - if(delimiter_index == 0) { - line = cx_strtrim(line); - if(line.length != 0) { - parser->error = 1; - } - } else { - cxstring n = cx_strn(buf, delimiter_index); - cxstring v = cx_strn( - buf + delimiter_index + 1, - line.length - delimiter_index - 1); - n = cx_strtrim(n); - v = cx_strtrim(v); - if(n.length != 0 || v.length != 0) { - *name = n; - *value = v; - parser->pos += i + 1; - return 1; - } else { - parser->error = 1; - } - } - - parser->pos += i + 1; - } - - return 0; -} - -int ucx_properties2map(UcxProperties *parser, CxMap *map) { - cxstring name; - cxstring value; - while(ucx_properties_next(parser, &name, &value)) { - cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value); - if(!mutvalue.ptr) { - return 1; - } - if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) { - cxFree(map->collection.allocator, mutvalue.ptr); - return 1; - } - } - if (parser->error) { - return parser->error; - } else { - return 0; - } -} - -// buffer size is documented - change doc, when you change bufsize! -#define UCX_PROPLOAD_BUFSIZE 1024 -int ucx_properties_load(CxMap *map, FILE *file) { - UcxProperties *parser = ucx_properties_new(); - if(!(parser && map && file)) { - return 1; - } - - int error = 0; - size_t r; - char buf[UCX_PROPLOAD_BUFSIZE]; - while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) { - ucx_properties_fill(parser, buf, r); - error = ucx_properties2map(parser, map); - if (error) { - break; - } - } - ucx_properties_free(parser); - return error; -} - -int ucx_properties_store(CxMap *map, FILE *file) { - CxMapIterator iter = cxMapIterator(map); - cxstring value; - size_t written; - - cx_foreach(CxMapEntry *, v, iter) { - value = cx_str(v->value); - - written = 0; - written += fwrite(v->key->data, 1, v->key->len, file); - written += fwrite(" = ", 1, 3, file); - written += fwrite(value.ptr, 1, value.length, file); - written += fwrite("\n", 1, 1, file); - - if (written != v->key->len + value.length + 4) { - return 1; - } - } - - return 0; -} -
--- a/ui/common/ucx_properties.h Fri Dec 12 12:00:34 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,223 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * @file properties.h - * - * Load / store utilities for properties files. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_PROPERTIES_H -#define UCX_PROPERTIES_H - -#include <cx/hash_map.h> -#include <cx/string.h> - -#include <stdio.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * UcxProperties object for parsing properties data. - * Most of the fields are for internal use only. You may configure the - * properties parser, e.g. by changing the used delimiter or specifying - * up to three different characters that shall introduce comments. - */ -typedef struct { - /** - * Input buffer (don't set manually). - * Automatically set by calls to ucx_properties_fill(). - */ - char *buffer; - - /** - * Length of the input buffer (don't set manually). - * Automatically set by calls to ucx_properties_fill(). - */ - size_t buflen; - - /** - * Current buffer position (don't set manually). - * Used by ucx_properties_next(). - */ - size_t pos; - - /** - * Internal temporary buffer (don't set manually). - * Used by ucx_properties_next(). - */ - char *tmp; - - /** - * Internal temporary buffer length (don't set manually). - * Used by ucx_properties_next(). - */ - size_t tmplen; - - /** - * Internal temporary buffer capacity (don't set manually). - * Used by ucx_properties_next(). - */ - size_t tmpcap; - - /** - * Parser error code. - * This is always 0 on success and a nonzero value on syntax errors. - * The value is set by ucx_properties_next(). - */ - int error; - - /** - * The delimiter that shall be used. - * This is '=' by default. - */ - char delimiter; - - /** - * The first comment character. - * This is '#' by default. - */ - char comment1; - - /** - * The second comment character. - * This is not set by default. - */ - char comment2; - - /** - * The third comment character. - * This is not set by default. - */ - char comment3; -} UcxProperties; - - -/** - * Constructs a new UcxProperties object. - * @return a pointer to the new UcxProperties object - */ -UcxProperties *ucx_properties_new(); - -/** - * Destroys a UcxProperties object. - * @param prop the UcxProperties object to destroy - */ -void ucx_properties_free(UcxProperties *prop); - -/** - * Sets the input buffer for the properties parser. - * - * After calling this function, you may parse the data by calling - * ucx_properties_next() until it returns 0. The function ucx_properties2map() - * is a convenience function that reads as much data as possible by using this - * function. - * - * - * @param prop the UcxProperties object - * @param buf a pointer to the new buffer - * @param len the payload length of the buffer - * @see ucx_properties_next() - * @see ucx_properties2map() - */ -void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); - -/** - * Retrieves the next key/value-pair. - * - * This function returns a nonzero value as long as there are key/value-pairs - * found. If no more key/value-pairs are found, you may refill the input buffer - * with ucx_properties_fill(). - * - * <b>Attention:</b> the cxstring.ptr pointers of the output parameters point to - * memory within the input buffer of the parser and will get invalid some time. - * If you want long term copies of the key/value-pairs, use sstrdup() after - * calling this function. - * - * @param prop the UcxProperties object - * @param name a pointer to the cxstring that shall contain the property name - * @param value a pointer to the cxstring that shall contain the property value - * @return Nonzero, if a key/value-pair was successfully retrieved - * @see ucx_properties_fill() - */ -int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value); - -/** - * Retrieves all available key/value-pairs and puts them into a UcxMap. - * - * This is done by successive calls to ucx_properties_next() until no more - * key/value-pairs can be retrieved. - * - * The memory for the map values is allocated by the map's own allocator. - * - * @param prop the UcxProperties object - * @param map the target map - * @return The UcxProperties.error code (i.e. 0 on success). - * @see ucx_properties_fill() - * @see UcxMap.allocator - */ -int ucx_properties2map(UcxProperties *prop, CxMap *map); - -/** - * Loads a properties file to a UcxMap. - * - * This is a convenience function that reads data from an input - * stream until the end of the stream is reached. - * - * @param map the map object to write the key/value-pairs to - * @param file the <code>FILE*</code> stream to read from - * @return 0 on success, or a non-zero value on error - * - * @see ucx_properties_fill() - * @see ucx_properties2map() - */ -int ucx_properties_load(CxMap *map, FILE *file); - -/** - * Stores a UcxMap to a file. - * - * The key/value-pairs are written by using the following format: - * - * <code>[key] = [value]\\n</code> - * - * @param map the map to store - * @param file the <code>FILE*</code> stream to write to - * @return 0 on success, or a non-zero value on error - */ -int ucx_properties_store(CxMap *map, FILE *file); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_PROPERTIES_H */ -