--- /dev/null
+/* -*-c-*-
+ *
+ * Grow a buffer if it's too small
+ *
+ * (c) 2024 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib 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 Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef MLIB_GROWBUF_H
+#define MLIB_GROWBUF_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifndef MLIB_ALLOC_H
+# include "alloc.h"
+#endif
+
+/*----- Macros provided ---------------------------------------------------*/
+
+/* --- @GROWBUF_LIMIT@ --- *
+ *
+ * Arguments: @size_t granule@ = allocation granule
+ *
+ * Returns: The largest number %$n$% such that the total size of %$n$%
+ * objects, each of size @granule@, can be represented in a
+ * @size_t@.
+ */
+
+#define GROWBUF_LIMIT(granule) (~(size_t)0/(granule))
+
+/* --- @GROWBUF_SIZE@ --- *
+ *
+ * Arguments: @size_t sz@ = the current size (updated)
+ * @size_t want@ = the desired minimum size
+ * @size_t init@ = a suitable initial size
+ * @size_t granule@ = the allocation granule size
+ *
+ * Returns: ---
+ *
+ * Use: On entry, @sz@ should be the current capacity of some buffer,
+ * in terms of objects of size @granule@, and @want@ a needed
+ * capacity, in the same terms, with @want > sz@; @init@ should
+ * be some suitable positive initial size, in case the current
+ * size is zero. The macro updates @sz@ to be some suitable new
+ * positive size at least as large as @want@.
+ */
+
+#define GROWBUF_SIZE(sz, want, init, granule) do { \
+ size_t _sz_ = (sz), _want_ = (want); \
+ \
+ assert(_want_ < GROWBUF_LIMIT(granule)/2); \
+ if (!_sz_) _sz_ = (init); \
+ while (_sz_ < _want_) _sz_ *= 2; \
+ (sz) = _sz_; \
+} while (0)
+
+/* --- @GROWBUF_EXTEND@, @GROWBUF_REPLACE@ --- *
+ *
+ * Arguments: @arena *a@ = pointer to an arena
+ * @type *buf@ = pointer to some buffer, possibly null (updated)
+ * @size_t sz@ = current buffer size (updated)
+ * @size_t want@ = desired minimum size
+ * @size_t init@ = a suitable initial size
+ * @size_t granule@ = the allocation granule size
+ *
+ * Returns: ---
+ *
+ * Use: On entry, @buf@ should be a pointer to a buffer, allocated
+ * from the arena @a@, with space for @sz@ objects of size
+ * @granule@; @buf@ may be null if @sz@ is zero. On exit, @buf@
+ * and @sz@ will be updated to refer to a possibly different
+ * buffer, with space for at least @want@ objects (but certainly
+ * not smaller than before).
+ *
+ * @GROWBUF_EXTEND@ preserves the contents of the buffer;
+ * @GROWBUF_REPLACE@ discards the existing contents.
+ */
+
+#define GROWBUF_EXTEND(a, buf, sz, want, init, granule) do { \
+ size_t _sz0 = (sz), _sz = _sz0, _want = (want), _gr = (granule); \
+ void *_p = (buf); \
+ arena *_a = (a); \
+ \
+ if (_sz < _want) { \
+ GROWBUF_SIZE(_sz, _want, init, _gr); \
+ if (!_p) _p = x_alloc(_a, _sz*_gr); \
+ else _p = x_realloc(_a, _p, _sz*_gr, _sz0*_gr); \
+ (buf) = _p; (sz) = _sz; \
+ } \
+} while (0)
+
+#define GROWBUF_REPLACE(a, buf, sz, want, init, granule) do { \
+ size_t _sz0 = (sz), _sz = _sz0, _want = (want), _gr = (granule); \
+ void *_p = (buf); \
+ arena *_a = (a); \
+ \
+ if (_sz < _want) { \
+ GROWBUF_SIZE(_sz, want, init, _gr); \
+ if (_p) x_free(_a, _p); \
+ _p = x_alloc(_a, _sz*_gr); \
+ (buf) = _p; (sz) = _sz; \
+ } \
+} while (0)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif