While making use of the 'string-buffer' module in GNU gettext, I noticed
that more API is needed, to cover the frequent use-cases.

For code that is not performance-critical, the benefits of this module are:
  - You need to declare one variable instead of 3 variables.
  - It moves the xrealloc logic out-of-sight.
  - In many cases it gets away with 1 memory allocation instead of 1x malloc
    and 1x or 2x realloc. In some cases it avoids heap memory allocation
    entirely.


2024-09-25  Bruno Haible  <br...@clisp.org>

        string-buffer: Add more API.
        * lib/string-buffer.h: Include string-desc.h.
        (sb_append1, sb_append_desc): New declarations.
        (sb_append_c): Renamed from sb_append.
        (sb_contents, sb_contents_c, sb_dupfree): New declarations.
        (sb_dupfree_c): Renamed from sb_dupfree.
        * lib/string-buffer.c (sb_append1, sb_append_desc): New functions.
        (sb_append_c): Renamed from sb_append.
        (sb_contents, sb_contents_c, sb_dupfree): New functions.
        (sb_dupfree_c): Renamed from sb_dupfree. Optimize.
        * modules/string-buffer (Depends-on): Add string-desc.
        * tests/test-string-buffer.c (main): Use sb_append_c instead of
        sb_append. Use sb_dupfree_c instead of sb_dupfree. Test also sb_append1,
        sb_append_desc, sb_contents.
        * NEWS: Mention the changes.

diff --git a/NEWS b/NEWS
index 40a8da647a..c3710825dd 100644
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,9 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2024-09-25  string-buffer   The function sb_append is renamed to sb_append_c.
+                            The function sb_dupfree is renamed to sb_dupfree_c.
+
 2024-08-14  verror          The include file is changed from "verror.h"
                             to <error.h>.
 
diff --git a/lib/string-buffer.c b/lib/string-buffer.c
index ed34e6b78b..b3759aa395 100644
--- a/lib/string-buffer.c
+++ b/lib/string-buffer.c
@@ -87,7 +87,33 @@ sb_ensure_more_bytes (struct string_buffer *buffer, size_t 
increment)
 }
 
 int
-sb_append (struct string_buffer *buffer, const char *str)
+sb_append1 (struct string_buffer *buffer, char c)
+{
+  if (sb_ensure_more_bytes (buffer, 1) < 0)
+    {
+      buffer->error = true;
+      return -1;
+    }
+  buffer->data[buffer->length++] = c;
+  return 0;
+}
+
+int
+sb_append_desc (struct string_buffer *buffer, string_desc_t s)
+{
+  size_t len = string_desc_length (s);
+  if (sb_ensure_more_bytes (buffer, len) < 0)
+    {
+      buffer->error = true;
+      return -1;
+    }
+  memcpy (buffer->data + buffer->length, string_desc_data (s), len);
+  buffer->length += len;
+  return 0;
+}
+
+int
+sb_append_c (struct string_buffer *buffer, const char *str)
 {
   size_t len = strlen (str);
   if (sb_ensure_more_bytes (buffer, len) < 0)
@@ -107,10 +133,57 @@ sb_free (struct string_buffer *buffer)
     free (buffer->data);
 }
 
-/* Returns the contents of BUFFER, and frees all other memory held
-   by BUFFER.  Returns NULL upon failure or if there was an error earlier.  */
-char *
+string_desc_t
+sb_contents (struct string_buffer *buffer)
+{
+  return string_desc_new_addr (buffer->length, buffer->data);
+}
+
+const char *
+sb_contents_c (struct string_buffer *buffer)
+{
+  if (sb_ensure_more_bytes (buffer, 1) < 0)
+    return NULL;
+  buffer->data[buffer->length] = '\0';
+
+  return buffer->data;
+}
+
+string_desc_t
 sb_dupfree (struct string_buffer *buffer)
+{
+  if (buffer->error)
+    goto fail;
+
+  size_t length = buffer->length;
+  if (buffer->data == buffer->space)
+    {
+      char *copy = (char *) malloc (length > 0 ? length : 1);
+      if (copy == NULL)
+        goto fail;
+      memcpy (copy, buffer->data, length);
+      return string_desc_new_addr (length, copy);
+    }
+  else
+    {
+      /* Shrink the string before returning it.  */
+      char *contents = buffer->data;
+      if (length < buffer->allocated)
+        {
+          contents = realloc (contents, length > 0 ? length : 1);
+          if (contents == NULL)
+            goto fail;
+        }
+      return string_desc_new_addr (length, contents);
+    }
+
+ fail:
+  sb_free (buffer);
+  return string_desc_new_addr (0, NULL);
+}
+
+char *
+sb_dupfree_c (struct string_buffer *buffer)
 {
   if (buffer->error)
     goto fail;
@@ -120,21 +193,22 @@ sb_dupfree (struct string_buffer *buffer)
   buffer->data[buffer->length] = '\0';
   buffer->length++;
 
+  size_t length = buffer->length;
   if (buffer->data == buffer->space)
     {
-      char *copy = (char *) malloc (buffer->length);
+      char *copy = (char *) malloc (length);
       if (copy == NULL)
         goto fail;
-      memcpy (copy, buffer->data, buffer->length);
+      memcpy (copy, buffer->data, length);
       return copy;
     }
   else
     {
       /* Shrink the string before returning it.  */
       char *contents = buffer->data;
-      if (buffer->length < buffer->allocated)
+      if (length < buffer->allocated)
         {
-          contents = realloc (contents, buffer->length);
+          contents = realloc (contents, length);
           if (contents == NULL)
             goto fail;
         }
diff --git a/lib/string-buffer.h b/lib/string-buffer.h
index 5476197d52..02ba90abfb 100644
--- a/lib/string-buffer.h
+++ b/lib/string-buffer.h
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 
 #include "attribute.h"
+#include "string-desc.h"
 
 typedef char * _GL_ATTRIBUTE_CAPABILITY_TYPE ("memory resource")
         sb_heap_allocated_pointer_t;
@@ -51,9 +52,17 @@ extern "C" {
 extern void sb_init (struct string_buffer *buffer)
   _GL_ATTRIBUTE_ACQUIRE_CAPABILITY (buffer->data);
 
-/* Appends the contents of STR to BUFFER.
+/* Appends the character C to BUFFER.
    Returns 0, or -1 in case of out-of-memory error.  */
-extern int sb_append (struct string_buffer *buffer, const char *str);
+extern int sb_append1 (struct string_buffer *buffer, char c);
+
+/* Appends the contents of the memory area STR to BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+extern int sb_append_desc (struct string_buffer *buffer, string_desc_t s);
+
+/* Appends the contents of the C string STR to BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+extern int sb_append_c (struct string_buffer *buffer, const char *str);
 
 /* Appends the result of the printf-compatible FORMATSTRING with the argument
    list LIST to BUFFER.
@@ -77,7 +86,7 @@ extern int sb_appendvf (struct string_buffer *buffer,
    Therefore, if the format string is valid and does not use %ls/%lc
    directives nor widths, the only possible error code is ENOMEM.  */
 extern int sb_appendf (struct string_buffer *buffer,
-                        const char *formatstring, ...)
+                       const char *formatstring, ...)
   #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__
   ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 3))
   #else
@@ -89,10 +98,29 @@ extern int sb_appendf (struct string_buffer *buffer,
 extern void sb_free (struct string_buffer *buffer)
   _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
 
-/* Returns the contents of BUFFER, and frees all other memory held
-   by BUFFER.  Returns NULL upon failure or if there was an error earlier.
+/* Returns a read-only view of the current contents of BUFFER.
+   The result is only valid until the next operation on BUFFER.  */
+extern string_desc_t sb_contents (struct string_buffer *buffer);
+
+/* Ensures the contents of BUFFER is followed by a NUL byte (without
+   incrementing the length of the contents).
+   Then returns a read-only view of the current contents of BUFFER,
+   that is, the current contents of BUFFER as a C string.
+   Returns NULL upon out-of-memory error.
+   The result is only valid until the next operation on BUFFER.  */
+extern const char * sb_contents_c (struct string_buffer *buffer);
+
+/* Returns the contents of BUFFER and frees all other memory held by BUFFER.
+   Returns NULL upon failure or if there was an error earlier.
+   It is the responsibility of the caller to string_desc_free() the result.  */
+extern string_desc_t sb_dupfree (struct string_buffer *buffer)
+  _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
+
+/* Returns the contents of BUFFER (with an added trailing NUL, that is,
+   as a C string), and frees all other memory held by BUFFER.
+   Returns NULL upon failure or if there was an error earlier.
    It is the responsibility of the caller to free() the result.  */
-extern char * sb_dupfree (struct string_buffer *buffer)
+extern char * sb_dupfree_c (struct string_buffer *buffer)
   _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
 
 #ifdef __cplusplus
diff --git a/modules/string-buffer b/modules/string-buffer
index 2010641b2b..21d3fdcf5b 100644
--- a/modules/string-buffer
+++ b/modules/string-buffer
@@ -9,6 +9,7 @@ lib/string-buffer-printf.c
 Depends-on:
 stdbool
 attribute
+string-desc
 stdarg
 vsnzprintf-posix
 
diff --git a/tests/test-string-buffer.c b/tests/test-string-buffer.c
index d492837b39..580263b656 100644
--- a/tests/test-string-buffer.c
+++ b/tests/test-string-buffer.c
@@ -44,12 +44,50 @@ char invalid_format_string_2[] = "%^";
 int
 main ()
 {
+  /* Test accumulation.  */
+  {
+    struct string_buffer buffer;
+
+    sb_init (&buffer);
+    sb_append1 (&buffer, 'x');
+    sb_append1 (&buffer, '\377');
+    char *s = sb_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "x\377") == 0);
+    free (s);
+  }
+  {
+    struct string_buffer buffer;
+
+    sb_init (&buffer);
+    sb_append1 (&buffer, 'x');
+    sb_append1 (&buffer, '\377');
+    {
+      string_desc_t sd = sb_contents (&buffer);
+      ASSERT (string_desc_length (sd) == 2);
+      ASSERT (string_desc_char_at (sd, 0) == 'x');
+      ASSERT (string_desc_char_at (sd, 1) == '\377');
+    }
+    sb_append1 (&buffer, '\0');
+    sb_append1 (&buffer, 'z');
+    {
+      string_desc_t sd = sb_contents (&buffer);
+      ASSERT (string_desc_length (sd) == 4);
+      ASSERT (string_desc_char_at (sd, 0) == 'x');
+      ASSERT (string_desc_char_at (sd, 1) == '\377');
+      ASSERT (string_desc_char_at (sd, 2) == '\0');
+      ASSERT (string_desc_char_at (sd, 3) == 'z');
+    }
+    char *s = sb_dupfree_c (&buffer);
+    ASSERT (s != NULL && memcmp (s, "x\377\0z\0", 5) == 0);
+    free (s);
+  }
+
   /* Test simple string concatenation.  */
   {
     struct string_buffer buffer;
 
     sb_init (&buffer);
-    char *s = sb_dupfree (&buffer);
+    char *s = sb_dupfree_c (&buffer);
     ASSERT (s != NULL && strcmp (s, "") == 0);
     free (s);
   }
@@ -58,23 +96,35 @@ main ()
     struct string_buffer buffer;
 
     sb_init (&buffer);
-    sb_append (&buffer, "abc");
-    sb_append (&buffer, "");
-    sb_append (&buffer, "defg");
-    char *s = sb_dupfree (&buffer);
+    sb_append_c (&buffer, "abc");
+    sb_append_c (&buffer, "");
+    sb_append_c (&buffer, "defg");
+    char *s = sb_dupfree_c (&buffer);
     ASSERT (s != NULL && strcmp (s, "abcdefg") == 0);
     free (s);
   }
 
+  {
+    struct string_buffer buffer;
+
+    sb_init (&buffer);
+    sb_append_c (&buffer, "abc");
+    sb_append_desc (&buffer, string_desc_new_addr (5, "de\0fg"));
+    sb_append_c (&buffer, "hij");
+    char *s = sb_dupfree_c (&buffer);
+    ASSERT (s != NULL && memcmp (s, "abcde\0fghij", 12) == 0);
+    free (s);
+  }
+
   /* Test printf-like formatting.  */
   {
     struct string_buffer buffer;
 
     sb_init (&buffer);
-    sb_append (&buffer, "<");
+    sb_append_c (&buffer, "<");
     sb_appendf (&buffer, "%x", 3735928559U);
-    sb_append (&buffer, ">");
-    char *s = sb_dupfree (&buffer);
+    sb_append_c (&buffer, ">");
+    char *s = sb_dupfree_c (&buffer);
     ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0);
     free (s);
   }
@@ -84,10 +134,10 @@ main ()
     struct string_buffer buffer;
 
     sb_init (&buffer);
-    sb_append (&buffer, "<");
+    sb_append_c (&buffer, "<");
     my_appendf (&buffer, "%x", 3735928559U);
-    sb_append (&buffer, ">");
-    char *s = sb_dupfree (&buffer);
+    sb_append_c (&buffer, ">");
+    char *s = sb_dupfree_c (&buffer);
     ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0);
     free (s);
   }
@@ -102,7 +152,7 @@ main ()
     int ret;
 
     sb_init (&buffer);
-    sb_append (&buffer, "<");
+    sb_append_c (&buffer, "<");
 
     ret = sb_appendf (&buffer, "%lc", (wint_t) 0x76543210);
     #if !defined _AIX
@@ -110,20 +160,20 @@ main ()
     ASSERT (errno == EILSEQ);
     #endif
 
-    sb_append (&buffer, "|");
+    sb_append_c (&buffer, "|");
 
     ret = sb_appendf (&buffer, invalid_format_string_1, 1);
     ASSERT (ret < 0);
     ASSERT (errno == EINVAL);
 
-    sb_append (&buffer, "|");
+    sb_append_c (&buffer, "|");
 
     ret = sb_appendf (&buffer, invalid_format_string_2, 2);
     ASSERT (ret < 0);
     ASSERT (errno == EINVAL);
 
-    sb_append (&buffer, ">");
-    char *s = sb_dupfree (&buffer);
+    sb_append_c (&buffer, ">");
+    char *s = sb_dupfree_c (&buffer);
     ASSERT (s == NULL);
   }
 




Reply via email to