changes cx_strcat() family of function to return an int and removes two unnecessary variants

Thu, 25 Dec 2025 11:10:13 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 25 Dec 2025 11:10:13 +0100
changeset 1667
608cc0b25352
parent 1665
b79405fbf91d
child 1668
3ffdfe1776b4

changes cx_strcat() family of function to return an int and removes two unnecessary variants

relates to #792

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/string.h.md file | annotate | diff | comparison | revisions
src/cx/json.h file | annotate | diff | comparison | revisions
src/cx/string.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
tests/test_string.c file | annotate | diff | comparison | revisions
--- 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()
--- 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()
--- 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 <cx/string.h>
 
-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.
 
--- 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);
--- 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.
--- 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);
 }
 
--- 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(
--- 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);

mercurial