From 993424547df0d253d546dbe7adee9b2448294b08 Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Sat, 15 Jun 2024 11:42:29 -0700
Subject: Add dynamically-sized strings.

---
 cstring/CMakeLists.txt    |   7 ++--
 cstring/include/cstring.h |  37 ++++++++++++++++-
 cstring/src/cstring.c     | 103 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 143 insertions(+), 4 deletions(-)
 create mode 100644 cstring/src/cstring.c

(limited to 'cstring')

diff --git a/cstring/CMakeLists.txt b/cstring/CMakeLists.txt
index df2fa45..bc385c3 100644
--- a/cstring/CMakeLists.txt
+++ b/cstring/CMakeLists.txt
@@ -2,11 +2,12 @@ cmake_minimum_required(VERSION 3.0)
 
 project(cstring)
 
-add_library(cstring INTERFACE)
+add_library(cstring
+  src/cstring.c)
 
-target_include_directories(cstring INTERFACE
+target_include_directories(cstring PUBLIC
   include)
 
-target_link_libraries(cstring INTERFACE
+target_link_libraries(cstring PUBLIC
   cassert
   -lbsd)
diff --git a/cstring/include/cstring.h b/cstring/include/cstring.h
index 134e68e..a3f6b3f 100644
--- a/cstring/include/cstring.h
+++ b/cstring/include/cstring.h
@@ -1,12 +1,16 @@
 /// Fixed-size strings with value semantics.
 #pragma once
 
-#include <bsd/string.h>
 #include <cassert.h>
+
+#include <bsd/string.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 
+// -----------------------------------------------------------------------------
+// Fix-sized strings.
+
 /// A fixed-size string.
 /// The string is null-terminated so that it can be used with the usual C APIs.
 #define DEF_STRING(STRING, SIZE)                                          \
@@ -118,3 +122,34 @@ DEF_STRING(sstring, 32)    // Small.
 DEF_STRING(mstring, 256)   // Medium.
 DEF_STRING(lstring, 1024)  // Large.
 DEF_STRING(xlstring, 4096) // Extra large.
+
+// -----------------------------------------------------------------------------
+// Dynamically-sized strings.
+
+typedef struct string {
+  const char* data;
+  size_t      length;
+} string;
+
+/// Create a new string.
+string string_new(const char*);
+
+/// Delete the string.
+void string_del(string*);
+
+/// Get the string's length.
+static inline size_t string_length(const string str) { return str.length; }
+
+/// Get the string's data.
+static inline const char* string_data(const string str) { return str.data; }
+
+/// Concatenate two strings.
+string string_concat(string left, string right);
+
+/// Convert a size to string.
+string string_from_size(size_t);
+
+/// Convert and format a size to string.
+/// The result uses B for bytes, K for kilobytes (1024), M for megabytes
+/// (2**20), and G for gigabytes (2**30), with two decimal digits.
+string string_format_size(size_t);
diff --git a/cstring/src/cstring.c b/cstring/src/cstring.c
new file mode 100644
index 0000000..832cb85
--- /dev/null
+++ b/cstring/src/cstring.c
@@ -0,0 +1,103 @@
+#include <cstring.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+string string_new(const char* cstr) {
+  const size_t length = strlen(cstr);
+  const size_t size   = length + 1;
+
+  char* data = calloc(size, sizeof(char));
+  ASSERT(data);
+  if (length > 0) {
+    memcpy(data, cstr, length);
+  }
+
+  return (string){
+      .data   = data,
+      .length = length,
+  };
+}
+
+void string_del(string* str) {
+  if (str->data) {
+    free((void*)str->data);
+    str->data   = 0;
+    str->length = 0;
+  }
+}
+
+string string_concat(string left, string right) {
+  const size_t length = left.length + right.length;
+  const size_t size   = length + 1;
+
+  char* data = calloc(size, sizeof(char));
+  ASSERT(data);
+  if (length > 0) {
+    memcpy(data, left.data, left.length);
+    memcpy(data + left.length, right.data, right.length);
+  }
+
+  return (string){
+      .data   = data,
+      .length = length,
+  };
+}
+
+string string_from_size(size_t size) {
+  const size_t length = snprintf(NULL, 0, "%zu", size) + 1;
+  char*        data   = calloc(length, sizeof(char));
+  ASSERT(data);
+  snprintf(data, length, "%zu", size);
+  return (string){
+      .data   = data,
+      .length = length,
+  };
+}
+
+string string_format_size(size_t size) {
+  const size_t multiples[] = {1073741824, 1048576, 1024, 1};
+  const char*  units[]     = {"G", "M", "K", "B"};
+
+  size_t integer    = 0;
+  size_t fractional = 0;
+
+  int i;
+  for (i = 0; i < 4; ++i) {
+    const size_t m = multiples[i];
+    if (size >= m) {
+      integer    = size / m;
+      fractional = size % m;
+      break;
+    }
+  }
+
+  double      s;
+  const char* unit;
+  const char* format;
+  if (i == 4) { // 0
+    s      = (double)size;
+    unit   = "";
+    format = "%f%s";
+  } else if (i == 3) { // Bytes
+    s      = (double)integer + (double)fractional / (double)multiples[i];
+    unit   = units[i];
+    format = "%.0f%s";
+  } else { // KB, MB, GB
+    assert(i >= 0);
+    assert(i < 3);
+    s      = (double)integer + (double)fractional / (double)multiples[i];
+    unit   = units[i];
+    format = "%.2f%s";
+  }
+
+  const size_t length = snprintf(NULL, 0, format, s, unit) + 1;
+  char*        data   = calloc(length, sizeof(char));
+  ASSERT(data);
+  snprintf(data, length, format, s, unit);
+  return (string){
+      .data   = data,
+      .length = length,
+  };
+}
-- 
cgit v1.2.3