From 098aca8fe945d5c220ce5bfaa5020972e0f685c1 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Sat, 28 Nov 2020 11:30:38 -0500
Subject: dstring: New dstrdcat(), dstrcatf(), dstrvcatf() functions

---
 dstring.c | 77 +++++++++++++++++++++++++++++++++++++++++++++------------------
 dstring.h | 43 ++++++++++++++++++++++++++++++++++-
 2 files changed, 97 insertions(+), 23 deletions(-)

diff --git a/dstring.c b/dstring.c
index 2ec0d72..475bf65 100644
--- a/dstring.c
+++ b/dstring.c
@@ -1,6 +1,6 @@
 /****************************************************************************
  * bfs                                                                      *
- * Copyright (C) 2016-2019 Tavian Barnes <tavianator@tavianator.com>        *
+ * Copyright (C) 2016-2020 Tavian Barnes <tavianator@tavianator.com>        *
  *                                                                          *
  * Permission to use, copy, modify, and/or distribute this software for any *
  * purpose with or without fee is hereby granted.                           *
@@ -124,6 +124,10 @@ int dstrncat(char **dest, const char *src, size_t n) {
 	return dstrcat_impl(dest, src, strnlen(src, n));
 }
 
+int dstrdcat(char **dest, const char *src) {
+	return dstrcat_impl(dest, src, dstrlen(src));
+}
+
 int dstrapp(char **str, char c) {
 	return dstrcat_impl(str, &c, 1);
 }
@@ -139,40 +143,69 @@ char *dstrprintf(const char *format, ...) {
 }
 
 char *dstrvprintf(const char *format, va_list args) {
-	// Guess a length to try to avoid calling vsnprintf() twice
-	int len, cap = 2*strlen(format);
-	char *str = dstralloc(cap);
+	// Guess a capacity to try to avoid reallocating
+	char *str = dstralloc(2*strlen(format));
 	if (!str) {
 		return NULL;
 	}
 
-	va_list args2;
-	va_copy(args2, args);
+	if (dstrvcatf(&str, format, args) != 0) {
+		dstrfree(str);
+		return NULL;
+	}
 
-	len = vsnprintf(str, cap + 1, format, args);
-	if (len > cap) {
-		if (dstreserve(&str, len) != 0) {
-			goto fail;
-		}
+	return str;
+}
 
-		cap = len;
-		len = vsnprintf(str, cap + 1, format, args2);
-		assert(len == cap);
-	}
+int dstrcatf(char **str, const char *format, ...) {
+	va_list args;
+
+	va_start(args, format);
+	int ret = dstrvcatf(str, format, args);
+	va_end(args);
 
-	va_end(args2);
+	return ret;
+}
+
+int dstrvcatf(char **str, const char *format, va_list args) {
+	// Guess a capacity to try to avoid calling vsnprintf() twice
+	size_t len = dstrlen(*str);
+	dstreserve(str, len + 2*strlen(format));
+	size_t cap = dstrheader(*str)->capacity;
 
-	if (len < 0) {
+	va_list copy;
+	va_copy(copy, args);
+
+	char *tail = *str + len;
+	int ret = vsnprintf(tail, cap - len + 1, format, args);
+	if (ret < 0) {
 		goto fail;
 	}
 
-	struct dstring *header = dstrheader(str);
-	header->length = len;
-	return str;
+	size_t tail_len = ret;
+	if (tail_len > cap - len) {
+		cap = len + tail_len;
+		if (dstreserve(str, cap) != 0) {
+			goto fail;
+		}
+
+		tail = *str + len;
+		ret = vsnprintf(tail, tail_len + 1, format, copy);
+		if (ret < 0 || (size_t)ret != tail_len) {
+			assert(false);
+			goto fail;
+		}
+	}
+
+	va_end(copy);
+
+	struct dstring *header = dstrheader(*str);
+	header->length += tail_len;
+	return 0;
 
 fail:
-	dstrfree(str);
-	return NULL;
+	*tail = '\0';
+	return -1;
 }
 
 void dstrfree(char *dstr) {
diff --git a/dstring.h b/dstring.h
index b556313..54106f3 100644
--- a/dstring.h
+++ b/dstring.h
@@ -1,6 +1,6 @@
 /****************************************************************************
  * bfs                                                                      *
- * Copyright (C) 2016-2019 Tavian Barnes <tavianator@tavianator.com>        *
+ * Copyright (C) 2016-2020 Tavian Barnes <tavianator@tavianator.com>        *
  *                                                                          *
  * Permission to use, copy, modify, and/or distribute this software for any *
  * purpose with or without fee is hereby granted.                           *
@@ -106,6 +106,18 @@ int dstrcat(char **dest, const char *src);
  */
 int dstrncat(char **dest, const char *src, size_t n);
 
+/**
+ * Append a dynamic string to another dynamic string.
+ *
+ * @param dest
+ *         The destination dynamic string.
+ * @param src
+ *         The dynamic string to append.
+ * @return
+ *         0 on success, -1 on failure.
+ */
+int dstrdcat(char **dest, const char *src);
+
 /**
  * Append a single character to a dynamic string.
  *
@@ -142,6 +154,35 @@ char *dstrprintf(const char *format, ...);
  */
 char *dstrvprintf(const char *format, va_list args);
 
+/**
+ * Format some text onto the end of a dynamic string.
+ *
+ * @param str
+ *         The destination dynamic string.
+ * @param format
+ *         The format string to fill in.
+ * @param ...
+ *         Any arguments for the format string.
+ * @return
+ *         0 on success, -1 on failure.
+ */
+BFS_FORMATTER(2, 3)
+int dstrcatf(char **str, const char *format, ...);
+
+/**
+ * Format some text from a va_list onto the end of a dynamic string.
+ *
+ * @param str
+ *         The destination dynamic string.
+ * @param format
+ *         The format string to fill in.
+ * @param args
+ *         The arguments for the format string.
+ * @return
+ *         0 on success, -1 on failure.
+ */
+int dstrvcatf(char **str, const char *format, va_list args);
+
 /**
  * Free a dynamic string.
  *
-- 
cgit v1.2.3