--- /dev/null
+/* charstring.h -- dynamically-sized char array that can report size
+ * in both characters and bytes
+ *
+ * This code is Copyright (c) 2017, by the authors of nmh. See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information. */
+
+/*
+ * char array that keeps track of size in both bytes and characters
+ * Usage note:
+ * Don't store return value of charstring_buffer() and use later
+ * after intervening push_back's; use charstring_buffer_copy()
+ * instead.
+ */
+
+typedef struct charstring *charstring_t;
+
+charstring_t charstring_create(size_t);
+charstring_t charstring_copy(const charstring_t) NONNULL(1);
+void charstring_free(charstring_t);
+/* Append a single-byte character: */
+void charstring_push_back(charstring_t, const char) NONNULL(1);
+/* Append possibly multi-byte character(s): */
+void charstring_push_back_chars(charstring_t, const char [], size_t) NONNULL(1);
+void charstring_append(charstring_t, const charstring_t) NONNULL(2);
+void charstring_append_cstring(charstring_t, const char []) NONNULL(2);
+void charstring_clear(charstring_t) NONNULL(1);
+/* Don't store return value of charstring_buffer() and use later after
+ intervening push_back's; use charstring_buffer_copy() instead. */
+const char *charstring_buffer(const charstring_t) NONNULL(1);
+/* User is responsible for free'ing result of buffer copy. */
+char *charstring_buffer_copy(const charstring_t) NONNULL(1);
+size_t charstring_bytes(const charstring_t) NONNULL(1) PURE;
+size_t charstring_chars(const charstring_t) NONNULL(1) PURE;
--- /dev/null
+/* charstring.c -- dynamically-sized char array that can report size
+ * in both characters and bytes
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh. See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+#include <h/mh.h>
+#include <h/utils.h>
+
+#define CHARSTRING_DEFAULT_SIZE 64
+
+struct charstring {
+ char *buffer; /* the char array, not always null-terminated */
+ size_t max; /* current size of the char array, in bytes */
+ char *cur; /* size in bytes = cur - buffer, without trailing null */
+};
+
+
+static void
+charstring_reserve(charstring_t s, size_t need)
+{
+ const size_t cur = s->cur - s->buffer;
+
+ while (need >= s->max - cur) {
+ /* Insufficient capacity, so double it. */
+ s->buffer = mh_xrealloc(s->buffer, s->max *= 2);
+ s->cur = s->buffer + cur;
+ }
+}
+
+/*
+ * max is in characters
+ */
+charstring_t
+charstring_create(size_t max)
+{
+ charstring_t s = mh_xcalloc(1, sizeof(*s));
+
+ s->max = max ? max : CHARSTRING_DEFAULT_SIZE;
+ s->cur = s->buffer = mh_xcalloc(s->max, 1);
+
+ return s;
+}
+
+charstring_t
+charstring_copy(const charstring_t src)
+{
+ const size_t num = src->cur - src->buffer;
+ charstring_t s = mh_xcalloc(1, sizeof(*s));
+
+ s->max = src->max;
+ s->buffer = mh_xcalloc(s->max, 1);
+ memcpy(s->buffer, src->buffer, num);
+ s->cur = s->buffer + num;
+
+ return s;
+}
+
+/*
+ * OK to call charstring_free with a NULL argument.
+ */
+void
+charstring_free(charstring_t s)
+{
+ if (s) {
+ free(s->buffer);
+ free(s);
+ }
+}
+
+void
+charstring_push_back(charstring_t s, const char c)
+{
+ charstring_reserve(s, s->cur - s->buffer + 1);
+ *s->cur++ = c;
+}
+
+/*
+ * num is the number of bytes in c
+ */
+void
+charstring_push_back_chars(charstring_t s, const char c[], size_t num)
+{
+ charstring_reserve(s, s->cur - s->buffer + num);
+ memcpy(s->cur, c, num);
+ s->cur += num;
+}
+
+void
+charstring_append(charstring_t dest, const charstring_t src)
+{
+ const size_t num = src->cur - src->buffer;
+
+ if (num > 0) {
+ charstring_reserve(dest, dest->cur - dest->buffer + num);
+ memcpy(dest->cur, src->buffer, num);
+ dest->cur += num;
+ }
+}
+
+void
+charstring_append_cstring(charstring_t dest, const char src[])
+{
+ const size_t num = strlen(src);
+
+ if (num > 0) {
+ charstring_reserve(dest, dest->cur - dest->buffer + num);
+ memcpy(dest->cur, src, num); /* Exclude src's trailing NUL. */
+ dest->cur += num;
+ }
+}
+
+void
+charstring_clear(charstring_t s)
+{
+ s->cur = s->buffer;
+}
+
+/*
+ * Don't store return value of charstring_buffer() and use later after
+ * intervening push_back's; use charstring_buffer_copy() instead.
+ */
+const char *
+charstring_buffer(const charstring_t s)
+{
+ charstring_reserve(s, s->cur - s->buffer + 1);
+
+ /* This is the only place that we null-terminate the buffer. */
+ *s->cur = '\0';
+ /* Don't increment cur so that more can be appended later, and so
+ that charstring_bytes() behaves as strlen() by not counting the
+ null. */
+
+ return s->buffer;
+}
+
+char *
+charstring_buffer_copy(const charstring_t s)
+{
+ char *copy = mh_xcalloc(s->cur - s->buffer + 1, 1);
+
+ /* Use charstring_buffer() to null terminate the buffer. */
+ memcpy(copy, charstring_buffer(s), s->cur - s->buffer + 1);
+
+ return copy;
+}
+
+size_t
+charstring_bytes(const charstring_t s)
+{
+ return s->cur - s->buffer;
+}