# HG changeset patch # User Mike Becker # Date 1766657413 -3600 # Node ID 608cc0b25352cddfe7aebaee9d9c47e186dc2a5e # Parent b79405fbf91d8016b812cddf03681b0b2aa64c23 changes cx_strcat() family of function to return an int and removes two unnecessary variants relates to #792 diff -r b79405fbf91d -r 608cc0b25352 CHANGELOG --- a/CHANGELOG Wed Dec 24 12:13:59 2025 +0100 +++ b/CHANGELOG Thu Dec 25 11:10:13 2025 +0100 @@ -21,6 +21,7 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible + * changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc() diff -r b79405fbf91d -r 608cc0b25352 docs/Writerside/topics/about.md --- a/docs/Writerside/topics/about.md Wed Dec 24 12:13:59 2025 +0100 +++ b/docs/Writerside/topics/about.md Thu Dec 25 11:10:13 2025 +0100 @@ -48,6 +48,7 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible +* changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc() diff -r b79405fbf91d -r 608cc0b25352 docs/Writerside/topics/string.h.md --- a/docs/Writerside/topics/string.h.md Wed Dec 24 12:13:59 2025 +0100 +++ b/docs/Writerside/topics/string.h.md Thu Dec 25 11:10:13 2025 +0100 @@ -119,27 +119,24 @@ ```C #include -cxmutstr cx_strcat(size_t count, ... ); - -cxmutstr cx_strcat_a(const CxAllocator *alloc, size_t count, ... ); +int cx_strcat(cxmutstr *str, size_t count, ... ); -cxmutstr cx_strcat_m(cxmutstr str, size_t count, ... ); - -cxmutstr cx_strcat_ma(const CxAllocator *alloc, - cxmutstr str, size_t count, ... ); +int cx_strcat_a(const CxAllocator *allocator, + cxmutstr *str, size_t count, ... ); size_t cx_strlen(size_t count, ...); ``` -The `cx_strcat_a()` function takes `count` UCX strings (`cxstring` or `cxmutstr`), -allocates memory for a concatenation of those strings _with a single allocation_, -and copies the contents of the strings to the new memory. +The `cx_strcat_a()` function takes `count` UCX strings (`cxstring` or `cxmutstr` - not pointers!), +allocates memory in `str` for a concatenation of those strings _with a single allocation_, +and appends the contents of the strings to `str`. `cx_strcat()` is equivalent, except that it uses the [default allocator](allocator.h.md#default-allocator). -The `cx_strcat_ma()` and `cx_strcat_m()` append the `count` strings to the specified string `str` and, -instead of allocating new memory, reallocate the existing memory in `str`. -If the pointer in `str` is `NULL`, there is no difference to `cx_strcat_a()`. -Note, that `count` always denotes the number of variadic arguments in _both_ variants. +Example usage: +```C +cxmutstr str = {0}; +cx_strcat(&str, 2, cx_str("Hello, "), cx_str("World!")); +``` The function `cx_strlen()` sums the length of the specified strings. diff -r b79405fbf91d -r 608cc0b25352 src/cx/json.h --- a/src/cx/json.h Wed Dec 24 12:13:59 2025 +0100 +++ b/src/cx/json.h Thu Dec 25 11:10:13 2025 +0100 @@ -289,13 +289,6 @@ CxBuffer buffer; /** - * Used internally. - * - * Remembers the prefix of the last uncompleted token. - */ - CxJsonToken uncompleted; - - /** * A pointer to an intermediate state of the currently parsed value. * * Never access this value manually. @@ -310,6 +303,16 @@ cxmutstr uncompleted_member_name; /** + * Internal buffer for uncompleted tokens. + */ + cxmutstr uncompleted_content; + + /** + * The expected type of the currently parsed, uncompleted token. + */ + CxJsonTokenType uncompleted_tokentype; + + /** * State stack. */ CX_ARRAY(int, states); diff -r b79405fbf91d -r 608cc0b25352 src/cx/string.h --- a/src/cx/string.h Wed Dec 24 12:13:59 2025 +0100 +++ b/src/cx/string.h Thu Dec 25 11:10:13 2025 +0100 @@ -416,9 +416,6 @@ * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * If memory allocation fails, the pointer in the returned string will - * be @c NULL. Depending on the allocator, @c errno might be set. - * * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. @@ -427,32 +424,12 @@ * @param str the string the other strings shall be concatenated to * @param count the number of the other following strings to concatenate * @param ... all other UCX strings - * @return the concatenated string + * @retval zero success + * @retval non-zero allocation failure */ -cx_attr_nodiscard cx_attr_nonnull -CX_EXPORT cxmutstr cx_strcat_ma(const CxAllocator *alloc, - cxmutstr str, size_t count, ...); - -/** - * Concatenates strings and returns a new string. - * - * The resulting string will be allocated by the specified allocator. - * So developers @em must pass the return value to cx_strfree_a() eventually. - * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL. Depending on the allocator, @c errno might be set. - * - * @note It is guaranteed that there is only one allocation for the - * resulting string. - * It is also guaranteed that the returned string is zero-terminated. - * - * @param alloc (@c CxAllocator*) the allocator to use - * @param count (@c size_t) the number of the other following strings to concatenate - * @param ... all other UCX strings - * @return (@c cxmutstr) the concatenated string - */ -#define cx_strcat_a(alloc, count, ...) \ - cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) +cx_attr_nonnull +CX_EXPORT int cx_strcat_a(const CxAllocator *alloc, + cxmutstr *str, size_t count, ...); /** * Concatenates strings and returns a new string. @@ -460,43 +437,18 @@ * The resulting string will be allocated by the cxDefaultAllocator. * So developers @em must pass the return value to cx_strfree() eventually. * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL and @c errno might be set. - * * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. * + * @param str (@c cxmutstr*) the string the other strings shall be concatenated to * @param count (@c size_t) the number of the other following strings to concatenate * @param ... all other UCX strings - * @return (@c cxmutstr) the concatenated string + * @retval zero success + * @retval non-zero allocation failure */ -#define cx_strcat(count, ...) \ - cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) - -/** - * Concatenates strings. - * - * The resulting string will be allocated by the cxDefaultAllocator. - * So developers @em must pass the return value to cx_strfree() eventually. - * - * If @p str already contains a string, the memory will be reallocated and - * the other strings are appended. Otherwise, new memory is allocated. - * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL and @c errno might be set. - * - * @note It is guaranteed that there is only one allocation for the - * resulting string. - * It is also guaranteed that the returned string is zero-terminated. - * - * @param str (@c cxmutstr) the string the other strings shall be concatenated to - * @param count (@c size_t) the number of the other following strings to concatenate - * @param ... all other strings - * @return (@c cxmutstr) the concatenated string - */ -#define cx_strcat_m(str, count, ...) \ - cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) +#define cx_strcat(str, count, ...) \ + cx_strcat_a(cxDefaultAllocator, str, count, __VA_ARGS__) /** * Returns a substring starting at the specified location. diff -r b79405fbf91d -r 608cc0b25352 src/json.c --- a/src/json.c Wed Dec 24 12:13:59 2025 +0100 +++ b/src/json.c Thu Dec 25 11:10:13 2025 +0100 @@ -96,23 +96,27 @@ } static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { - cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); - bool allocated = false; - if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { + cxmutstr buf_str = cx_mutstrn(json->buffer.space + start, end - start); + cxmutstr str; + bool allocated; + if (json->uncompleted_tokentype != CX_JSON_NO_TOKEN) { allocated = true; - str = cx_strcat_m(json->uncompleted.content, 1, str); - if (str.ptr == NULL) { // LCOV_EXCL_START - return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; - } // LCOV_EXCL_STOP + str = json->uncompleted_content; + if (cx_strcat(&str, 1, buf_str)) { + return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; // LCOV_EXCL_LINE + } + json->uncompleted_content = (cxmutstr){NULL, 0}; + json->uncompleted_tokentype = CX_JSON_NO_TOKEN; + } else { + allocated = false; + str = buf_str; } - json->uncompleted = (CxJsonToken){0}; CxJsonTokenType ttype; if (isstring) { ttype = CX_JSON_TOKEN_STRING; } else { - cxstring s = cx_strcast(str); - if (!cx_strcmp(s, "true") || !cx_strcmp(s, "false") - || !cx_strcmp(s, "null")) { + if (!cx_strcmp(str, "true") || !cx_strcmp(str, "false") + || !cx_strcmp(str, "null")) { ttype = CX_JSON_TOKEN_LITERAL; } else { ttype = token_numbertype(str.ptr, str.length); @@ -162,16 +166,16 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { // check if there is data in the buffer if (cxBufferEof(&json->buffer)) { - return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? + return json->uncompleted_tokentype == CX_JSON_NO_TOKEN ? CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; } // current token type and start index - CxJsonTokenType ttype = json->uncompleted.tokentype; + CxJsonTokenType ttype = json->uncompleted_tokentype; size_t token_part_start = json->buffer.pos; bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING - && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; + && json->uncompleted_content.ptr[json->uncompleted_content.length-1] == '\\'; for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { char c = json->buffer.space[i]; @@ -232,31 +236,23 @@ 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) { - // current token is uncompleted - // save current token content - CxJsonToken uncompleted = { - ttype, true, - cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) - }; - if (uncompleted.content.ptr == NULL) { + cxstring uncompleted = cx_strn(json->buffer.space + token_part_start, json->buffer.size - token_part_start); + if (json->uncompleted_tokentype == CX_JSON_NO_TOKEN) { + assert(json->uncompleted_content.ptr == NULL); + json->uncompleted_content = cx_strdup(uncompleted); + if (json->uncompleted_content.ptr == NULL) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } - json->uncompleted = uncompleted; + json->uncompleted_tokentype = ttype; } else { // previously we also had an uncompleted token // combine the uncompleted token with the current token - assert(json->uncompleted.allocated); - cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, - cx_strn(json->buffer.space + token_part_start, uncompleted_len)); - if (str.ptr == NULL) { + if (cx_strcat(&json->uncompleted_content, 1, uncompleted)) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } - json->uncompleted.content = str; } // advance the buffer position - we saved the stuff in the uncompleted token - json->buffer.pos += uncompleted_len; + json->buffer.pos += uncompleted.length; return CX_JSON_INCOMPLETE_DATA; } } @@ -564,7 +560,8 @@ } cxJsonValueFree(json->parsed); json->parsed = NULL; - token_destroy(&json->uncompleted); + json->uncompleted_tokentype = CX_JSON_NO_TOKEN; + cx_strfree(&json->uncompleted_content); cx_strfree_a(json->allocator, &json->uncompleted_member_name); } diff -r b79405fbf91d -r 608cc0b25352 src/string.c --- a/src/string.c Wed Dec 24 12:13:59 2025 +0100 +++ b/src/string.c Thu Dec 25 11:10:13 2025 +0100 @@ -99,13 +99,13 @@ return size; } -cxmutstr cx_strcat_ma( +int cx_strcat_a( const CxAllocator *alloc, - cxmutstr str, + cxmutstr *str, size_t count, ... ) { - if (count == 0) return str; + if (count == 0) return 0; va_list ap; va_start(ap, count); va_list ap2; @@ -113,7 +113,7 @@ // compute overall length bool overflow = false; - size_t slen = str.length; + size_t slen = str->length; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap, cxstring); if (slen > SIZE_MAX - s.length) overflow = true; @@ -125,36 +125,31 @@ if (overflow) { va_end(ap2); errno = EOVERFLOW; - return (cxmutstr) { NULL, 0 }; + return -1; } // reallocate or create new string - char *newstr; - if (str.ptr == NULL) { - newstr = cxMalloc(alloc, slen + 1); - } else { - newstr = cxRealloc(alloc, str.ptr, slen + 1); + if (cxReallocate(alloc, &str->ptr, slen + 1)) { + // LCOV_EXCL_START + va_end(ap2); + return -1; + // LCOV_EXCL_STOP } - if (newstr == NULL) { // LCOV_EXCL_START - va_end(ap2); - return (cxmutstr) {NULL, 0}; - } // LCOV_EXCL_STOP - str.ptr = newstr; // concatenate strings - size_t pos = str.length; - str.length = slen; + size_t pos = str->length; + str->length = slen; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap2, cxstring); - memcpy(str.ptr + pos, s.ptr, s.length); + memcpy(str->ptr + pos, s.ptr, s.length); pos += s.length; } va_end(ap2); // terminate string - str.ptr[str.length] = '\0'; + str->ptr[str->length] = '\0'; - return str; + return 0; } cxstring cx_strsubs( diff -r b79405fbf91d -r 608cc0b25352 tests/test_string.c --- a/tests/test_string.c Wed Dec 24 12:13:59 2025 +0100 +++ b/tests/test_string.c Thu Dec 25 11:10:13 2025 +0100 @@ -376,22 +376,26 @@ CxAllocator *alloc = &talloc.base; CX_TEST_DO { - cxmutstr t1 = cx_strcat_a(alloc, 2, s1, s2); + cxmutstr t1 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t1, 2, s1, s2)); CX_TEST_ASSERT(0 == cx_strcmp(t1, "1234")); ASSERT_ZERO_TERMINATED(t1); cx_strfree_a(alloc, &t1); - cxmutstr t2 = cx_strcat_a(alloc, 3, s1, s2, s3); + cxmutstr t2 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t2, 3, s1, s2, s3)); CX_TEST_ASSERT(0 == cx_strcmp(t2, "123456")); ASSERT_ZERO_TERMINATED(t2); cx_strfree_a(alloc, &t2); - cxmutstr t3 = cx_strcat_a(alloc, 6, s1, sn, s2, sn, s3, sn); + cxmutstr t3 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t3, 6, s1, sn, s2, sn, s3, sn)); CX_TEST_ASSERT(0 == cx_strcmp(t3, "123456")); ASSERT_ZERO_TERMINATED(t3); cx_strfree_a(alloc, &t3); - cxmutstr t4 = cx_strcat_a(alloc, 2, sn, sn); + cxmutstr t4 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t4, 2, sn, sn)); CX_TEST_ASSERT(0 == cx_strcmp(t4, "")); ASSERT_ZERO_TERMINATED(t4); cx_strfree_a(alloc, &t4); @@ -399,14 +403,15 @@ CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); // use the macro - cxmutstr t5 = cx_strcat(3, s3, s1, s2); + cxmutstr t5 = {0}; + CX_TEST_ASSERT(0 == cx_strcat(&t5, 3, s3, s1, s2)); CX_TEST_ASSERT(0 == cx_strcmp(t5, "561234")); ASSERT_ZERO_TERMINATED(t5); cx_strfree(&t5); // use an initial string cxmutstr t6 = cx_strdup(cx_str("Hello")); - t6 = cx_strcat_m(t6, 2, cx_str(", "), cx_str("World!")); + CX_TEST_ASSERT(0 == cx_strcat(&t6, 2, cx_str(", "), cx_str("World!"))); CX_TEST_ASSERT(0 == cx_strcmp(t6, "Hello, World!")); ASSERT_ZERO_TERMINATED(t6); cx_strfree(&t6); @@ -417,7 +422,8 @@ cxstring b = cx_strn(fakestr, SIZE_MAX / 3 - 5); cxstring c = cx_strn(fakestr, SIZE_MAX / 3 + 20); errno = 0; - cxmutstr z = cx_strcat(3, a, b, c); + cxmutstr z = {0}; + CX_TEST_ASSERT(0 != cx_strcat(&z, 3, a, b, c)); CX_TEST_ASSERT(errno == EOVERFLOW); CX_TEST_ASSERT(z.ptr == NULL); CX_TEST_ASSERT(z.length == 0); @@ -437,7 +443,8 @@ cxstring s9 = cx_str("xy"); CX_TEST_DO { - cxmutstr r = cx_strcat(9, s1, s2, s3, s4, s5, s6, s7, s8, s9); + cxmutstr r = {0}; + CX_TEST_ASSERT(0 == cx_strcat(&r, 9, s1, s2, s3, s4, s5, s6, s7, s8, s9)); CX_TEST_ASSERT(0 == cx_strcmp(r, "123456789abcdef0xy")); ASSERT_ZERO_TERMINATED(r); cx_strfree(&r);