* 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 <bruno@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 <bruno@clisp.org>
string-buffer: Improve comments.
--- /dev/null
+/* 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 <bruno@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;
+}
--- /dev/null
+/* 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 <bruno@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;
+}
--- /dev/null
+/* 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 <bruno@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 */
--- /dev/null
+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