The module 'string-buffer' supports creating a string piece by piece,
from the start to the end.

But in some cases, one needs to create a string piecemeal, from the end
to the start. Same thing, just in the opposite direction.

The C++ library authors would extend the 'string-buffer' type to allow
both appending and prepending. But that would deteriorate the performance
of the 'string-buffer' module. And allowing both appending and prepending
on the *same* creation is a very unlikely requirement.

So, the simple solution is create a module, like 'string-buffer', just
in the opposite direction.


2025-02-05  Bruno Haible  <br...@clisp.org>

        xstring-buffer-reversed: New module.
        * lib/xstring-buffer-reversed.c: New file, based on
        lib/xstring-buffer.c.
        * lib/xstring-buffer-reversed-printf.c: New file, based on
        lib/xstring-buffer-printf.c.
        * modules/xstring-buffer-reversed: New file.

2025-02-05  Bruno Haible  <br...@clisp.org>

        string-buffer-reversed: Add tests.
        * tests/test-string-buffer-reversed.c: New file, based on
        tests/test-string-buffer.c.
        * modules/string-buffer-reversed-tests: New file.

        string-buffer-reversed: New module.
        * lib/string-buffer-reversed.h: New file, based on lib/string-buffer.h.
        * lib/string-buffer-reversed.c: New file, based on lib/string-buffer.c.
        * lib/string-buffer-reversed-printf.c: New file, based on
        lib/string-buffer-printf.c.
        * modules/string-buffer-reversed: New file.

2025-02-05  Bruno Haible  <br...@clisp.org>

        string-buffer: Improve comments.
        * lib/string-buffer.h (struct string_buffer): Add a comment.

>From c9c29d13bf7022e1974fadd87db25b3e3a0e104e Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 5 Feb 2025 20:38:35 +0100
Subject: [PATCH 1/4] string-buffer: Improve comments.

* lib/string-buffer.h (struct string_buffer): Add a comment.
---
 ChangeLog           | 5 +++++
 lib/string-buffer.h | 1 +
 2 files changed, 6 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index f1b421c505..5db0072a97 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2025-02-05  Bruno Haible  <br...@clisp.org>
+
+	string-buffer: Improve comments.
+	* lib/string-buffer.h (struct string_buffer): Add a comment.
+
 2025-02-04  Bruno Haible  <br...@clisp.org>
 
 	gnulib-tool: Fix the result of --create-testdir (regression yesterday).
diff --git a/lib/string-buffer.h b/lib/string-buffer.h
index 78ce736c02..48c40eb3de 100644
--- a/lib/string-buffer.h
+++ b/lib/string-buffer.h
@@ -38,6 +38,7 @@ typedef char * _GL_ATTRIBUTE_CAPABILITY_TYPE ("memory resource")
 /* A string buffer type.  */
 struct string_buffer
 {
+  /* data[0 .. length-1] are used.  */
   sb_heap_allocated_pointer_t data;
   size_t length;     /* used bytes, <= allocated */
   size_t allocated;  /* allocated bytes */
-- 
2.43.0

>From ffb8a55ee7f7abe7c266c8d0386b77e8000518af Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 5 Feb 2025 20:57:16 +0100
Subject: [PATCH 2/4] string-buffer-reversed: New module.

* lib/string-buffer-reversed.h: New file, based on lib/string-buffer.h.
* lib/string-buffer-reversed.c: New file, based on lib/string-buffer.c.
* lib/string-buffer-reversed-printf.c: New file, based on
lib/string-buffer-printf.c.
* modules/string-buffer-reversed: New file.
---
 ChangeLog                           |   9 ++
 lib/string-buffer-reversed-printf.c | 207 ++++++++++++++++++++++++++
 lib/string-buffer-reversed.c        | 222 ++++++++++++++++++++++++++++
 lib/string-buffer-reversed.h        | 202 +++++++++++++++++++++++++
 modules/string-buffer-reversed      |  29 ++++
 5 files changed, 669 insertions(+)
 create mode 100644 lib/string-buffer-reversed-printf.c
 create mode 100644 lib/string-buffer-reversed.c
 create mode 100644 lib/string-buffer-reversed.h
 create mode 100644 modules/string-buffer-reversed

diff --git a/ChangeLog b/ChangeLog
index 5db0072a97..5e01a5dfb4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2025-02-05  Bruno Haible  <br...@clisp.org>
+
+	string-buffer-reversed: New module.
+	* lib/string-buffer-reversed.h: New file, based on lib/string-buffer.h.
+	* lib/string-buffer-reversed.c: New file, based on lib/string-buffer.c.
+	* lib/string-buffer-reversed-printf.c: New file, based on
+	lib/string-buffer-printf.c.
+	* modules/string-buffer-reversed: New file.
+
 2025-02-05  Bruno Haible  <br...@clisp.org>
 
 	string-buffer: Improve comments.
diff --git a/lib/string-buffer-reversed-printf.c b/lib/string-buffer-reversed-printf.c
new file mode 100644
index 0000000000..76db571cf3
--- /dev/null
+++ b/lib/string-buffer-reversed-printf.c
@@ -0,0 +1,207 @@
+/* A buffer that accumulates a string by piecewise concatenation, from the end
+   to the start.
+   Copyright (C) 2021-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "string-buffer-reversed.h"
+
+/* Undocumented.  */
+extern int sbr_ensure_more_bytes (struct string_buffer_reversed *buffer,
+                                  size_t increment);
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+sbr_prependvf (struct string_buffer_reversed *buffer, const char *formatstring,
+               va_list list)
+{
+  va_list list_copy;
+
+  /* Make a bit of room, so that the probability that the first vsnzprintf()
+     call succeeds is high.  */
+  size_t room = buffer->allocated - buffer->length;
+  if (room < 64)
+    {
+      if (sbr_ensure_more_bytes (buffer, 64) < 0)
+        {
+          buffer->error = true;
+          errno = ENOMEM;
+          return -1;
+        }
+      room = buffer->allocated - buffer->length;
+    }
+
+  va_copy (list_copy, list);
+
+  /* First vsnzprintf() call.  */
+  ptrdiff_t ret = vsnzprintf (buffer->data, room, formatstring, list);
+  if (ret < 0)
+    {
+      /* Failed.  errno is set.  */
+      buffer->error = true;
+      ret = -1;
+    }
+  else
+    {
+      if (ret <= room)
+        {
+          /* The result has fit into room bytes.  */
+          memmove (buffer->data + buffer->allocated - buffer->length - (size_t) ret,
+                   buffer->data,
+                   (size_t) ret);
+          buffer->length += (size_t) ret;
+          ret = 0;
+        }
+      else
+        {
+          /* The result was truncated.  Make more room, for a second
+             vsnzprintf() call.  */
+          if (sbr_ensure_more_bytes (buffer, (size_t) ret) < 0)
+            {
+              buffer->error = true;
+              errno = ENOMEM;
+              ret = -1;
+            }
+          else
+            {
+              /* Second vsnzprintf() call.  */
+              room = buffer->allocated - buffer->length;
+              ret = vsnzprintf (buffer->data, room, formatstring, list_copy);
+              if (ret < 0)
+                {
+                  /* Failed.  errno is set.  */
+                  buffer->error = true;
+                  ret = -1;
+                }
+              else
+                {
+                  if (ret <= room)
+                    {
+                      /* The result has fit into room bytes.  */
+                      memmove (buffer->data + buffer->allocated - buffer->length - (size_t) ret,
+                               buffer->data,
+                               (size_t) ret);
+                      buffer->length += (size_t) ret;
+                      ret = 0;
+                    }
+                  else
+                    /* The return values of the vsnzprintf() calls are not
+                       consistent.  */
+                    abort ();
+                }
+            }
+        }
+    }
+
+  va_end (list_copy);
+  return ret;
+}
+
+int
+sbr_prependf (struct string_buffer_reversed *buffer, const char *formatstring,
+              ...)
+{
+  va_list args;
+
+  /* Make a bit of room, so that the probability that the first vsnzprintf()
+     call succeeds is high.  */
+  size_t room = buffer->allocated - buffer->length;
+  if (room < 64)
+    {
+      if (sbr_ensure_more_bytes (buffer, 64) < 0)
+        {
+          buffer->error = true;
+          errno = ENOMEM;
+          return -1;
+        }
+      room = buffer->allocated - buffer->length;
+    }
+
+  va_start (args, formatstring);
+
+  /* First vsnzprintf() call.  */
+  ptrdiff_t ret = vsnzprintf (buffer->data, room, formatstring, args);
+  if (ret < 0)
+    {
+      /* Failed.  errno is set.  */
+      buffer->error = true;
+      ret = -1;
+    }
+  else
+    {
+      if (ret <= room)
+        {
+          /* The result has fit into room bytes.  */
+          memmove (buffer->data + buffer->allocated - buffer->length - (size_t) ret,
+                   buffer->data,
+                   (size_t) ret);
+          buffer->length += (size_t) ret;
+          ret = 0;
+        }
+      else
+        {
+          /* The result was truncated.  Make more room, for a second
+             vsnzprintf() call.  */
+          if (sbr_ensure_more_bytes (buffer, (size_t) ret) < 0)
+            {
+              buffer->error = true;
+              errno = ENOMEM;
+              ret = -1;
+            }
+          else
+            {
+              /* Second vsnzprintf() call.  */
+              room = buffer->allocated - buffer->length;
+              va_end (args);
+              va_start (args, formatstring);
+              ret = vsnzprintf (buffer->data, room, formatstring, args);
+              if (ret < 0)
+                {
+                  /* Failed.  errno is set.  */
+                  buffer->error = true;
+                  ret = -1;
+                }
+              else
+                {
+                  if (ret <= room)
+                    {
+                      /* The result has fit into room bytes.  */
+                      memmove (buffer->data + buffer->allocated - buffer->length - (size_t) ret,
+                               buffer->data,
+                               (size_t) ret);
+                      buffer->length += (size_t) ret;
+                      ret = 0;
+                    }
+                  else
+                    /* The return values of the vsnzprintf() calls are not
+                       consistent.  */
+                    abort ();
+                }
+            }
+        }
+    }
+
+  va_end (args);
+  return ret;
+}
diff --git a/lib/string-buffer-reversed.c b/lib/string-buffer-reversed.c
new file mode 100644
index 0000000000..a2bce83b9c
--- /dev/null
+++ b/lib/string-buffer-reversed.c
@@ -0,0 +1,222 @@
+/* A buffer that accumulates a string by piecewise concatenation, from the end
+   to the start.
+   Copyright (C) 2021-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "string-buffer-reversed.h"
+
+/* Undocumented.  */
+extern int sbr_ensure_more_bytes (struct string_buffer_reversed *buffer,
+                                  size_t increment);
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* The warnings about memory resource 'buffer->data' in this file are not
+   relevant.  Silence them.  */
+#if __clang_major__ >= 3
+# pragma clang diagnostic ignored "-Wthread-safety"
+#endif
+
+void
+sbr_init (struct string_buffer_reversed *buffer)
+{
+  buffer->data = buffer->space;
+  /* Pre-allocate a trailing NUL.  This makes it easy to implement
+     sbr_contents_c().  */
+  buffer->data[sizeof (buffer->space) - 1] = '\0';
+  buffer->length = 1;
+  buffer->allocated = sizeof (buffer->space);
+  buffer->error = false;
+}
+
+/* Ensures that INCREMENT bytes are available beyond the current used length
+   of BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+int
+sbr_ensure_more_bytes (struct string_buffer_reversed *buffer, size_t increment)
+{
+  size_t incremented_length = increment + buffer->length;
+  if (incremented_length < increment)
+    /* Overflow.  */
+    return -1;
+
+  if (buffer->allocated < incremented_length)
+    {
+      size_t new_allocated = 2 * buffer->allocated;
+      if (new_allocated < buffer->allocated)
+        /* Overflow.  */
+        return -1;
+      if (new_allocated < incremented_length)
+        new_allocated = incremented_length;
+
+      char *new_data;
+      if (buffer->data == buffer->space)
+        {
+          new_data = (char *) malloc (new_allocated);
+          if (new_data == NULL)
+            /* Out-of-memory.  */
+            return -1;
+          memcpy (new_data + new_allocated - buffer->length,
+                  buffer->data + buffer->allocated - buffer->length,
+                  buffer->length);
+        }
+      else
+        {
+          new_data = (char *) realloc (buffer->data, new_allocated);
+          if (new_data == NULL)
+            /* Out-of-memory.  */
+            return -1;
+          memmove (new_data + new_allocated - buffer->length,
+                   new_data + buffer->allocated - buffer->length,
+                   buffer->length);
+        }
+      buffer->data = new_data;
+      buffer->allocated = new_allocated;
+    }
+  return 0;
+}
+
+int
+sbr_prepend1 (struct string_buffer_reversed *buffer, char c)
+{
+  if (sbr_ensure_more_bytes (buffer, 1) < 0)
+    {
+      buffer->error = true;
+      return -1;
+    }
+  buffer->data[buffer->allocated - buffer->length - 1] = c;
+  buffer->length++;
+  return 0;
+}
+
+int
+sbr_prepend_desc (struct string_buffer_reversed *buffer, string_desc_t s)
+{
+  size_t len = sd_length (s);
+  if (sbr_ensure_more_bytes (buffer, len) < 0)
+    {
+      buffer->error = true;
+      return -1;
+    }
+  memcpy (buffer->data + buffer->allocated - buffer->length - len, sd_data (s), len);
+  buffer->length += len;
+  return 0;
+}
+
+int
+sbr_prepend_c (struct string_buffer_reversed *buffer, const char *str)
+{
+  size_t len = strlen (str);
+  if (sbr_ensure_more_bytes (buffer, len) < 0)
+    {
+      buffer->error = true;
+      return -1;
+    }
+  memcpy (buffer->data + buffer->allocated - buffer->length - len, str, len);
+  buffer->length += len;
+  return 0;
+}
+
+void
+sbr_free (struct string_buffer_reversed *buffer)
+{
+  if (buffer->data != buffer->space)
+    free (buffer->data);
+}
+
+string_desc_t
+sbr_contents (struct string_buffer_reversed *buffer)
+{
+  return sd_new_addr (buffer->length - 1,
+                      buffer->data + buffer->allocated - buffer->length);
+}
+
+const char *
+sbr_contents_c (struct string_buffer_reversed *buffer)
+{
+  return buffer->data + buffer->allocated - buffer->length;
+}
+
+string_desc_t
+sbr_dupfree (struct string_buffer_reversed *buffer)
+{
+  if (buffer->error)
+    goto fail;
+
+  size_t length = buffer->length;
+  if (buffer->data == buffer->space)
+    {
+      char *copy = (char *) malloc (length > 1 ? length - 1 : 1);
+      if (copy == NULL)
+        goto fail;
+      memcpy (copy, buffer->data + buffer->allocated - length, length - 1);
+      return sd_new_addr (length - 1, copy);
+    }
+  else
+    {
+      /* Shrink the string before returning it.  */
+      char *contents = buffer->data;
+      memmove (contents, contents + buffer->allocated - length, length - 1);
+      contents = realloc (contents, length > 1 ? length - 1 : 1);
+      if (contents == NULL)
+        goto fail;
+      return sd_new_addr (length - 1, contents);
+    }
+
+ fail:
+  sbr_free (buffer);
+  return sd_new_addr (0, NULL);
+}
+
+char *
+sbr_dupfree_c (struct string_buffer_reversed *buffer)
+{
+  if (buffer->error)
+    goto fail;
+
+  size_t length = buffer->length;
+  if (buffer->data == buffer->space)
+    {
+      char *copy = (char *) malloc (length);
+      if (copy == NULL)
+        goto fail;
+      memcpy (copy, buffer->data + buffer->allocated - length, length);
+      return copy;
+    }
+  else
+    {
+      /* Shrink the string before returning it.  */
+      char *contents = buffer->data;
+      if (length < buffer->allocated)
+        {
+          memmove (contents, contents + buffer->allocated - length, length);
+          contents = realloc (contents, length);
+          if (contents == NULL)
+            goto fail;
+        }
+      return contents;
+    }
+
+ fail:
+  sbr_free (buffer);
+  return NULL;
+}
diff --git a/lib/string-buffer-reversed.h b/lib/string-buffer-reversed.h
new file mode 100644
index 0000000000..7fe05e4869
--- /dev/null
+++ b/lib/string-buffer-reversed.h
@@ -0,0 +1,202 @@
+/* A buffer that accumulates a string by piecewise concatenation, from the end
+   to the start.
+   Copyright (C) 2021-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#ifndef _STRING_BUFFER_REVERSED_H
+#define _STRING_BUFFER_REVERSED_H
+
+/* This file uses _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_RETURNS_NONNULL,
+   _GL_ATTRIBUTE_CAPABILITY_TYPE, _GL_ATTRIBUTE_ACQUIRE_CAPABILITY,
+   _GL_ATTRIBUTE_RELEASE_CAPABILITY.  */
+#if !_GL_CONFIG_H_INCLUDED
+ #error "Please include config.h first."
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "attribute.h"
+#include "string-desc.h"
+
+typedef char * _GL_ATTRIBUTE_CAPABILITY_TYPE ("memory resource")
+        sbr_heap_allocated_pointer_t;
+
+/* A string buffer type.  */
+struct string_buffer_reversed
+{
+  /* data[allocated-length .. allocated-1] are used.  */
+  sbr_heap_allocated_pointer_t data;
+  size_t length;     /* used bytes, <= allocated */
+  size_t allocated;  /* allocated bytes */
+  bool error;        /* true if there was an error */
+  char space[1024];  /* stack allocated space */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ================== Functions in module 'string-buffer' ================== */
+
+/* Initializes BUFFER to the empty string.  */
+extern void sbr_init (struct string_buffer_reversed *buffer)
+  _GL_ATTRIBUTE_ACQUIRE_CAPABILITY (buffer->data);
+
+/* Prepends the character C to BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+extern int sbr_prepend1 (struct string_buffer_reversed *buffer, char c);
+
+/* Prepends the contents of the memory area S to BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+extern int sbr_prepend_desc (struct string_buffer_reversed *buffer,
+                            string_desc_t s);
+
+/* Prepends the contents of the C string STR to BUFFER.
+   Returns 0, or -1 in case of out-of-memory error.  */
+extern int sbr_prepend_c (struct string_buffer_reversed *buffer,
+                         const char *str);
+
+/* Prepends the result of the printf-compatible FORMATSTRING with the argument
+   list LIST to BUFFER.
+   Returns 0, or -1 with errno set in case of error.
+   Error code EOVERFLOW can only occur when a width > INT_MAX is used.
+   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 sbr_prependvf (struct string_buffer_reversed *buffer,
+                          const char *formatstring, va_list list)
+  #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__
+  ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 0))
+  #else
+  ATTRIBUTE_FORMAT ((__printf__, 2, 0))
+  #endif
+  ;
+
+/* Prepends the result of the printf-compatible FORMATSTRING with the following
+   arguments to BUFFER.
+   Returns 0, or -1 with errno set in case of error.
+   Error code EOVERFLOW can only occur when a width > INT_MAX is used.
+   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 sbr_prependf (struct string_buffer_reversed *buffer,
+                         const char *formatstring, ...)
+  #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__
+  ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 3))
+  #else
+  ATTRIBUTE_FORMAT ((__printf__, 2, 3))
+  #endif
+  ;
+
+/* Frees the memory held by BUFFER.  */
+extern void sbr_free (struct string_buffer_reversed *buffer)
+  _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
+
+/* 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 sbr_contents (struct string_buffer_reversed *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.
+   The result is only valid until the next operation on BUFFER.  */
+extern const char * sbr_contents_c (struct string_buffer_reversed *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 sd_free() the result.  */
+extern string_desc_t sbr_dupfree (struct string_buffer_reversed *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 * sbr_dupfree_c (struct string_buffer_reversed *buffer)
+  _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
+
+/* ================== Functions in module 'xstring-buffer' ================== */
+
+#if GNULIB_XSTRING_BUFFER_REVERSED
+
+/* The following functions invoke xalloc_die () in case of out-of-memory
+   error.  */
+
+/* Prepends the character C to BUFFER.  */
+extern void sbr_xprepend1 (struct string_buffer_reversed *buffer, char c);
+
+/* Prepends the contents of the memory area S to BUFFER.  */
+extern void sbr_xprepend_desc (struct string_buffer_reversed *buffer,
+                               string_desc_t s);
+
+/* Prepends the contents of the C string STR to BUFFER.  */
+extern void sbr_xprepend_c (struct string_buffer_reversed *buffer,
+                            const char *str);
+
+/* Prepends the result of the printf-compatible FORMATSTRING with the argument
+   list LIST to BUFFER.
+   Returns 0, or -1 in case of error other than out-of-memory error.
+   Error code EOVERFLOW can only occur when a width > INT_MAX is used.
+   Therefore, if the format string is valid and does not use %ls/%lc
+   directives nor widths, no error is possible.  */
+extern int sbr_xprependvf (struct string_buffer_reversed *buffer,
+                           const char *formatstring, va_list list)
+  #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__
+  ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 0))
+  #else
+  ATTRIBUTE_FORMAT ((__printf__, 2, 0))
+  #endif
+  ;
+
+/* Prepends the result of the printf-compatible FORMATSTRING with the following
+   arguments to BUFFER.
+   Returns 0, or -1 in case of error other than out-of-memory error.
+   Error code EOVERFLOW can only occur when a width > INT_MAX is used.
+   Therefore, if the format string is valid and does not use %ls/%lc
+   directives nor widths, no error is possible.  */
+extern int sbr_xprependf (struct string_buffer_reversed *buffer,
+                          const char *formatstring, ...)
+  #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__
+  ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 3))
+  #else
+  ATTRIBUTE_FORMAT ((__printf__, 2, 3))
+  #endif
+  ;
+
+/* Returns the contents of BUFFER and frees all other memory held by BUFFER.
+   Returns (0, NULL) if there was an error earlier.
+   It is the responsibility of the caller to sd_free() the result.  */
+extern string_desc_t sbr_xdupfree (struct string_buffer_reversed *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 if there was an error earlier.
+   It is the responsibility of the caller to free() the result.  */
+extern char * sbr_xdupfree_c (struct string_buffer_reversed *buffer)
+  _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE
+  _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data);
+
+#endif /* GNULIB_XSTRING_BUFFER_REVERSED */
+
+/* ========================================================================== */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _STRING_BUFFER_REVERSED_H */
diff --git a/modules/string-buffer-reversed b/modules/string-buffer-reversed
new file mode 100644
index 0000000000..a4ac8d5d86
--- /dev/null
+++ b/modules/string-buffer-reversed
@@ -0,0 +1,29 @@
+Description:
+A buffer that accumulates a string by piecewise concatenation,
+from the end to the start.
+
+Files:
+lib/string-buffer-reversed.h
+lib/string-buffer-reversed.c
+lib/string-buffer-reversed-printf.c
+
+Depends-on:
+bool
+attribute
+string-desc
+stdarg-h
+vsnzprintf-posix
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += string-buffer-reversed.c string-buffer-reversed-printf.c
+
+Include:
+"string-buffer-reversed.h"
+
+License:
+LGPL
+
+Maintainer:
+all
-- 
2.43.0

>From 1ee7224e6dac99d66401c9dfe37892fd44e265c8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 5 Feb 2025 20:59:04 +0100
Subject: [PATCH 3/4] string-buffer-reversed: Add tests.

* tests/test-string-buffer-reversed.c: New file, based on
tests/test-string-buffer.c.
* modules/string-buffer-reversed-tests: New file.
---
 ChangeLog                            |   5 +
 modules/string-buffer-reversed-tests |  11 ++
 tests/test-string-buffer-reversed.c  | 182 +++++++++++++++++++++++++++
 3 files changed, 198 insertions(+)
 create mode 100644 modules/string-buffer-reversed-tests
 create mode 100644 tests/test-string-buffer-reversed.c

diff --git a/ChangeLog b/ChangeLog
index 5e01a5dfb4..98f756de9a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2025-02-05  Bruno Haible  <br...@clisp.org>
 
+	string-buffer-reversed: Add tests.
+	* tests/test-string-buffer-reversed.c: New file, based on
+	tests/test-string-buffer.c.
+	* modules/string-buffer-reversed-tests: New file.
+
 	string-buffer-reversed: New module.
 	* lib/string-buffer-reversed.h: New file, based on lib/string-buffer.h.
 	* lib/string-buffer-reversed.c: New file, based on lib/string-buffer.c.
diff --git a/modules/string-buffer-reversed-tests b/modules/string-buffer-reversed-tests
new file mode 100644
index 0000000000..3590e4cdac
--- /dev/null
+++ b/modules/string-buffer-reversed-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-string-buffer-reversed.c
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-string-buffer-reversed
+check_PROGRAMS += test-string-buffer-reversed
diff --git a/tests/test-string-buffer-reversed.c b/tests/test-string-buffer-reversed.c
new file mode 100644
index 0000000000..39428e6ca5
--- /dev/null
+++ b/tests/test-string-buffer-reversed.c
@@ -0,0 +1,182 @@
+/* Test of buffer that accumulates a string by piecewise concatenation.
+   Copyright (C) 2021-2025 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2021.  */
+
+#include <config.h>
+
+#include "string-buffer-reversed.h"
+
+#include <errno.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "macros.h"
+
+static int
+my_prependf (struct string_buffer_reversed *buffer, const char *formatstring,
+             ...)
+{
+  va_list args;
+
+  va_start (args, formatstring);
+  int ret = sbr_prependvf (buffer, formatstring, args);
+  va_end (args);
+
+  return ret;
+}
+
+char invalid_format_string_1[] = "%&";
+char invalid_format_string_2[] = "%^";
+
+int
+main ()
+{
+  /* Test accumulation.  */
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend1 (&buffer, '\377');
+    sbr_prepend1 (&buffer, 'x');
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "x\377") == 0);
+    free (s);
+  }
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend1 (&buffer, '\377');
+    sbr_prepend1 (&buffer, 'x');
+    {
+      string_desc_t sd = sbr_contents (&buffer);
+      ASSERT (sd_length (sd) == 2);
+      ASSERT (sd_char_at (sd, 0) == 'x');
+      ASSERT (sd_char_at (sd, 1) == '\377');
+    }
+    sbr_prepend1 (&buffer, '\0');
+    sbr_prepend1 (&buffer, 'z');
+    {
+      string_desc_t sd = sbr_contents (&buffer);
+      ASSERT (sd_length (sd) == 4);
+      ASSERT (sd_char_at (sd, 0) == 'z');
+      ASSERT (sd_char_at (sd, 1) == '\0');
+      ASSERT (sd_char_at (sd, 2) == 'x');
+      ASSERT (sd_char_at (sd, 3) == '\377');
+    }
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && memcmp (s, "z\0x\377\0", 5) == 0);
+    free (s);
+  }
+
+  /* Test simple string concatenation.  */
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "") == 0);
+    free (s);
+  }
+
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend_c (&buffer, "efg");
+    sbr_prepend_c (&buffer, "");
+    sbr_prepend_c (&buffer, "abcd");
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "abcdefg") == 0);
+    free (s);
+  }
+
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend_c (&buffer, "hij");
+    sbr_prepend_desc (&buffer, sd_new_addr (5, "de\0fg"));
+    sbr_prepend_c (&buffer, "abc");
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && memcmp (s, "abcde\0fghij", 12) == 0);
+    free (s);
+  }
+
+  /* Test printf-like formatting.  */
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend_c (&buffer, ">");
+    sbr_prependf (&buffer, "%x", 3735928559U);
+    sbr_prepend_c (&buffer, "<");
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0);
+    free (s);
+  }
+
+  /* Test vprintf-like formatting.  */
+  {
+    struct string_buffer_reversed buffer;
+
+    sbr_init (&buffer);
+    sbr_prepend_c (&buffer, ">");
+    my_prependf (&buffer, "%x", 3735928559U);
+    sbr_prepend_c (&buffer, "<");
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0);
+    free (s);
+  }
+
+  /* Test printf-like formatting failure.
+     On all systems except AIX, trying to convert the wide-character 0x76543210
+     to a multibyte string (in the "C" locale) fails.
+     On all systems, invalid format directives make the vsnzprintf() call
+     fail.  */
+  {
+    struct string_buffer_reversed buffer;
+    int ret;
+
+    sbr_init (&buffer);
+    sbr_prepend_c (&buffer, ">");
+
+    ret = sbr_prependf (&buffer, "%lc", (wint_t) 0x76543210);
+    #if !(defined _AIX || (defined _WIN32 && !defined __CYGWIN__))
+    ASSERT (ret < 0);
+    ASSERT (errno == EILSEQ);
+    #endif
+
+    sbr_prepend_c (&buffer, "|");
+
+    ret = sbr_prependf (&buffer, invalid_format_string_1, 1);
+    ASSERT (ret < 0);
+    ASSERT (errno == EINVAL);
+
+    sbr_prepend_c (&buffer, "|");
+
+    ret = sbr_prependf (&buffer, invalid_format_string_2, 2);
+    ASSERT (ret < 0);
+    ASSERT (errno == EINVAL);
+
+    sbr_prepend_c (&buffer, "<");
+    char *s = sbr_dupfree_c (&buffer);
+    ASSERT (s == NULL);
+  }
+
+  return test_exit_status;
+}
-- 
2.43.0

>From 6b6d8daa76ec8ddf29504dff29f67b93c85a0442 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 5 Feb 2025 21:04:34 +0100
Subject: [PATCH 4/4] xstring-buffer-reversed: New module.

* lib/xstring-buffer-reversed.c: New file, based on
lib/xstring-buffer.c.
* lib/xstring-buffer-reversed-printf.c: New file, based on
lib/xstring-buffer-printf.c.
* modules/xstring-buffer-reversed: New file.
---
 ChangeLog                            |  9 ++++
 lib/xstring-buffer-reversed-printf.c | 52 ++++++++++++++++++++
 lib/xstring-buffer-reversed.c        | 73 ++++++++++++++++++++++++++++
 modules/xstring-buffer-reversed      | 25 ++++++++++
 4 files changed, 159 insertions(+)
 create mode 100644 lib/xstring-buffer-reversed-printf.c
 create mode 100644 lib/xstring-buffer-reversed.c
 create mode 100644 modules/xstring-buffer-reversed

diff --git a/ChangeLog b/ChangeLog
index 98f756de9a..0f03055688 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2025-02-05  Bruno Haible  <br...@clisp.org>
+
+	xstring-buffer-reversed: New module.
+	* lib/xstring-buffer-reversed.c: New file, based on
+	lib/xstring-buffer.c.
+	* lib/xstring-buffer-reversed-printf.c: New file, based on
+	lib/xstring-buffer-printf.c.
+	* modules/xstring-buffer-reversed: New file.
+
 2025-02-05  Bruno Haible  <br...@clisp.org>
 
 	string-buffer-reversed: Add tests.
diff --git a/lib/xstring-buffer-reversed-printf.c b/lib/xstring-buffer-reversed-printf.c
new file mode 100644
index 0000000000..32a927ee5e
--- /dev/null
+++ b/lib/xstring-buffer-reversed-printf.c
@@ -0,0 +1,52 @@
+/* Error-checking functions on a string buffer that accumulates from the end.
+   Copyright (C) 2024-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "string-buffer-reversed.h"
+
+#include <errno.h>
+
+#include "xalloc.h"
+
+int
+sbr_xprependvf (struct string_buffer_reversed *buffer,
+                const char *formatstring, va_list list)
+{
+  if (sbr_prependvf (buffer, formatstring, list) < 0)
+    {
+      if (errno == ENOMEM)
+        xalloc_die ();
+      return -1;
+    }
+  return 0;
+}
+
+int
+sbr_xprependf (struct string_buffer_reversed *buffer,
+               const char *formatstring, ...)
+{
+  va_list args;
+  int ret;
+
+  va_start (args, formatstring);
+  ret = sbr_xprependvf (buffer, formatstring, args);
+  va_end (args);
+  return ret;
+}
diff --git a/lib/xstring-buffer-reversed.c b/lib/xstring-buffer-reversed.c
new file mode 100644
index 0000000000..411f858782
--- /dev/null
+++ b/lib/xstring-buffer-reversed.c
@@ -0,0 +1,73 @@
+/* Error-checking functions on a string buffer that accumulates from the end.
+   Copyright (C) 2024-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "string-buffer-reversed.h"
+
+#include "xalloc.h"
+
+void
+sbr_xprepend1 (struct string_buffer_reversed *buffer, char c)
+{
+  if (sbr_prepend1 (buffer, c) < 0)
+    xalloc_die ();
+}
+
+void
+sbr_xprepend_desc (struct string_buffer_reversed *buffer, string_desc_t s)
+{
+  if (sbr_prepend_desc (buffer, s) < 0)
+    xalloc_die ();
+}
+
+void
+sbr_xprepend_c (struct string_buffer_reversed *buffer, const char *str)
+{
+  if (sbr_prepend_c (buffer, str) < 0)
+    xalloc_die ();
+}
+
+string_desc_t
+sbr_xdupfree (struct string_buffer_reversed *buffer)
+{
+  if (buffer->error)
+    {
+      sbr_free (buffer);
+      return sd_new_addr (0, NULL);
+    }
+  string_desc_t contents = sbr_dupfree (buffer);
+  if (sd_data (contents) == NULL)
+    xalloc_die ();
+  return contents;
+}
+
+char *
+sbr_xdupfree_c (struct string_buffer_reversed *buffer)
+{
+  if (buffer->error)
+    {
+      sbr_free (buffer);
+      return NULL;
+    }
+  char *contents = sbr_dupfree_c (buffer);
+  if (contents == NULL)
+    xalloc_die ();
+  return contents;
+}
diff --git a/modules/xstring-buffer-reversed b/modules/xstring-buffer-reversed
new file mode 100644
index 0000000000..f4b8091d27
--- /dev/null
+++ b/modules/xstring-buffer-reversed
@@ -0,0 +1,25 @@
+Description:
+Error-checking functions on a string buffer.
+
+Files:
+lib/xstring-buffer-reversed.c
+lib/xstring-buffer-reversed-printf.c
+
+Depends-on:
+string-buffer-reversed
+xalloc-die
+
+configure.ac:
+gl_MODULE_INDICATOR([xstring-buffer-reversed])
+
+Makefile.am:
+lib_SOURCES += xstring-buffer-reversed.c xstring-buffer-reversed-printf.c
+
+Include:
+"string-buffer-reversed.h"
+
+License:
+GPL
+
+Maintainer:
+all
-- 
2.43.0

Reply via email to