@@@ man wip
[mLib] / mem / growbuf.h
diff --git a/mem/growbuf.h b/mem/growbuf.h
new file mode 100644 (file)
index 0000000..ec78bcd
--- /dev/null
@@ -0,0 +1,137 @@
+/* -*-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