shithub: orca

Download patch

ref: 14c8610315e980655e7e9db9e5907ca1a7a0c4e9
parent: 34d04ad4c3672a1ab0eac8b985184239414c098e
author: cancel <cancel@cancel.fm>
date: Sat Jan 11 03:18:46 EST 2020

Change to use altnerate heap string implementation.

`sdd` was the first attempt at making one of these
individually-allocated string management/utility things. I didn't end up
liking the design, so I tried again from scratch, and called the new one
`oso`. Let's see how that one turns out. The name might change again in
the future, though I feel better about the design of this one.

--- a/term_util.c
+++ b/term_util.c
@@ -1,5 +1,5 @@
 #include "term_util.h"
-#include "sdd.h"
+#include "oso.h"
 #include <ctype.h>
 #include <form.h>
 #include <menu.h>
@@ -564,7 +564,8 @@
 
 void qform_add_text_line(Qform* qf, int id, char const* initial) {
   FIELD* f = new_field(1, 30, 0, 0, 0, 0);
-  set_field_buffer(f, 0, initial);
+  if (initial)
+    set_field_buffer(f, 0, initial);
   set_field_userptr(f, (void*)(intptr_t)(id));
   field_opts_off(f, O_WRAP | O_BLANK | O_STATIC);
   qf->ncurses_fields[qf->fields_count] = f;
@@ -665,7 +666,7 @@
   return NULL;
 }
 
-bool qform_get_text_line(Qform const* qf, int id, sdd** out) {
+bool qform_get_text_line(Qform const* qf, int id, oso** out) {
   FIELD* f = qform_find_field(qf, id);
   if (!f)
     return false;
@@ -674,6 +675,6 @@
   if (!buf)
     return false;
   Usz trimmed = size_without_trailing_spaces(buf);
-  *out = *out ? sdd_cpylen(*out, buf, trimmed) : sdd_newlen(buf, trimmed);
-  return (bool)*out;
+  osoputlen(out, buf, trimmed);
+  return osolen(*out) > 0;
 }
--- a/term_util.h
+++ b/term_util.h
@@ -4,7 +4,7 @@
 
 #define CTRL_PLUS(c) ((c)&037)
 
-struct sdd;
+struct oso;
 
 typedef enum {
   C_natural,
@@ -143,6 +143,6 @@
 void qform_push_to_nav(Qform* qf);
 void qform_set_title(Qform* qf, char const* title);
 bool qform_drive(Qform* qf, int key, Qform_action* out_action);
-bool qform_get_text_line(Qform const* qf, int id, struct sdd** out);
+bool qform_get_text_line(Qform const* qf, int id, struct oso** out);
 
 extern Qnav_stack qnav_stack;
--- /dev/null
+++ b/thirdparty/oso.c
@@ -1,0 +1,232 @@
+#include "oso.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute)
+#if __has_attribute(noinline) && __has_attribute(noclone)
+#define OSO_NOINLINE __attribute__((noinline, noclone))
+#elif __has_attribute(noinline)
+#define OSO_NOINLINE __attribute__((noinline))
+#endif
+#elif defined(_MSC_VER)
+#define OSO_NOINLINE __declspec(noinline)
+#endif
+#ifndef OSO_NOINLINE
+#define OSO_NOINLINE
+#endif
+
+#define OSO_INTERNAL OSO_NOINLINE static
+#define OSO_HDR(s) ((oso_header *)s - 1)
+#define OSO_CAP_MAX (SIZE_MAX - (sizeof(oso_header) + 1))
+
+typedef struct oso {
+  size_t len;
+  size_t cap;
+} oso_header;
+
+OSO_INTERNAL oso *oso_impl_reallochdr(oso_header *hdr, size_t new_cap) {
+  if (hdr) {
+    oso_header *new_hdr = realloc(hdr, sizeof(oso_header) + new_cap + 1);
+    if (!new_hdr) {
+      free(hdr);
+      return NULL;
+    }
+    new_hdr->cap = new_cap;
+    return new_hdr + 1;
+  }
+  hdr = malloc(sizeof(oso_header) + new_cap + 1);
+  if (!hdr)
+    return NULL;
+  hdr->len = 0;
+  hdr->cap = new_cap;
+  ((char *)(hdr + 1))[0] = '\0';
+  return hdr + 1;
+}
+OSO_INTERNAL oso *oso_impl_catvprintf(oso *s, char const *fmt, va_list ap) {
+  size_t old_len;
+  int required;
+  va_list cpy;
+  va_copy(cpy, ap);
+  required = vsnprintf(NULL, 0, fmt, cpy);
+  va_end(cpy);
+  osomakeroomfor(&s, (size_t)required);
+  if (!s)
+    return NULL;
+  old_len = OSO_HDR(s)->len;
+  vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap);
+  OSO_HDR(s)->len = old_len + (size_t)required;
+  return s;
+}
+
+OSO_NOINLINE
+void osoensurecap(oso **p, size_t new_cap) {
+  oso *s = *p;
+  if (new_cap > OSO_CAP_MAX) {
+    if (s) {
+      free(OSO_HDR(s));
+      *p = NULL;
+    }
+    return;
+  }
+  oso_header *hdr = NULL;
+  if (s) {
+    hdr = OSO_HDR(s);
+    if (hdr->cap >= new_cap)
+      return;
+  }
+  *p = oso_impl_reallochdr(hdr, new_cap);
+}
+
+OSO_NOINLINE
+void osomakeroomfor(oso **p, size_t add_len) {
+  oso *s = *p;
+  oso_header *hdr = NULL;
+  size_t new_cap;
+  if (s) {
+    hdr = OSO_HDR(s);
+    size_t len = hdr->len, cap = hdr->cap;
+    if (len > OSO_CAP_MAX - add_len) { // overflow, goodnight
+      free(hdr);
+      *p = NULL;
+      return;
+    }
+    new_cap = len + add_len;
+    if (cap >= new_cap)
+      return;
+  } else {
+    if (add_len > OSO_CAP_MAX)
+      return;
+    new_cap = add_len;
+  }
+  *p = oso_impl_reallochdr(hdr, new_cap);
+}
+
+void osoput(oso **p, char const *restrict cstr) {
+  osoputlen(p, cstr, strlen(cstr));
+}
+
+OSO_NOINLINE
+void osoputlen(oso **p, char const *restrict cstr, size_t len) {
+  oso *s = *p;
+  osoensurecap(&s, len);
+  if (s) {
+    OSO_HDR(s)->len = len;
+    memcpy((char *)s, cstr, len);
+    ((char *)s)[len] = '\0';
+  }
+  *p = s;
+}
+void osoputoso(oso **p, oso const *other) {
+  if (!other)
+    return;
+  osoputlen(p, (char const *)other, OSO_HDR(other)->len);
+}
+void osoputvprintf(oso **p, char const *fmt, va_list ap) {
+  *p = oso_impl_catvprintf(*p, fmt, ap);
+}
+void osoputprintf(oso **p, char const *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  *p = oso_impl_catvprintf(*p, fmt, ap);
+  va_end(ap);
+}
+void osocat(oso **p, char const *cstr) { osocatlen(p, cstr, strlen(cstr)); }
+OSO_NOINLINE
+void osocatlen(oso **p, char const *cstr, size_t len) {
+  oso *s = *p;
+  osomakeroomfor(&s, len);
+  if (s) {
+    oso_header *hdr = OSO_HDR(s);
+    size_t curr_len = hdr->len;
+    memcpy((char *)s + curr_len, cstr, len);
+    ((char *)s)[curr_len + len] = '\0';
+    hdr->len = curr_len + len;
+  }
+  *p = s;
+}
+void osocatoso(oso **p, oso const *other) {
+  if (!other)
+    return;
+  osocatlen(p, (char const *)other, OSO_HDR(other)->len);
+}
+void osocatvprintf(oso **p, char const *fmt, va_list ap) {
+  oso *s = *p;
+  if (s) {
+    OSO_HDR(s)->len = 0;
+    ((char *)s)[0] = '\0';
+  }
+  *p = oso_impl_catvprintf(s, fmt, ap);
+}
+void osocatprintf(oso **p, char const *fmt, ...) {
+  oso *s = *p;
+  if (s) {
+    OSO_HDR(s)->len = 0;
+    ((char *)s)[0] = '\0';
+  }
+  va_list ap;
+  va_start(ap, fmt);
+  *p = oso_impl_catvprintf(s, fmt, ap);
+  va_end(ap);
+}
+void osoclear(oso **p) {
+  oso *s = *p;
+  if (!s)
+    return;
+  OSO_HDR(s)->len = 0;
+  ((char *)s)[0] = '\0';
+}
+void osofree(oso *s) {
+  if (s)
+    free(OSO_HDR(s));
+}
+void osowipe(oso **p) {
+  osofree(*p);
+  *p = NULL;
+}
+void ososwap(oso const **a, oso const **b) {
+  oso const *tmp = *a;
+  *a = *b;
+  *b = tmp;
+}
+size_t osolen(oso const *s) { return s ? OSO_HDR(s)->len : 0; }
+size_t osocap(oso const *s) { return s ? OSO_HDR(s)->cap : 0; }
+void osolencap(oso const *s, size_t *out_len, size_t *out_cap) {
+  if (!s) {
+    *out_len = 0;
+    *out_cap = 0;
+    return;
+  }
+  oso_header *hdr = OSO_HDR(s);
+  *out_len = hdr->len;
+  *out_cap = hdr->cap;
+}
+size_t osoavail(oso const *s) {
+  if (!s)
+    return 0;
+  oso_header *h = OSO_HDR(s);
+  return h->cap - h->len;
+}
+
+void osotrim(oso *restrict s, char const *restrict cut_set) {
+  if (!s)
+    return;
+  char *str, *start, *end, *start_pos, *end_pos;
+  start_pos = start = str = (char *)s;
+  end_pos = end = str + OSO_HDR(s)->len - 1;
+  while (start_pos <= end && strchr(cut_set, *start_pos))
+    start_pos++;
+  while (end_pos > start_pos && strchr(cut_set, *end_pos))
+    end_pos--;
+  size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1);
+  OSO_HDR(s)->len = len;
+  if (str != start_pos)
+    memmove(str, start_pos, len);
+  str[len] = '\0';
+}
+
+#undef OSO_HDR
+#undef OSO_NOINLINE
+#undef OSO_CAP_MAX
+#undef OSO_INTERNAL
--- /dev/null
+++ b/thirdparty/oso.h
@@ -1,0 +1,182 @@
+#pragma once
+// Heap-allocated string handling.
+// Inspired by antirez's sds and gingerBill's gb_string.h.
+//
+// "I need null-terminated strings to interact with libc and/or POSIX, and my
+// software isn't serious enough to warrant using arena or page allocation
+// strategies. Manually fussing with null-terminated strings with libc sucks,
+// even though we're allocating everything individually on the heap! Why can't
+// we at least get a nicer interface for the trade-off we've already made?"
+//
+//                                EXAMPLE
+//                               ---------
+//   oso *mystring = NULL;
+//   osoput(&mystring, "Hello World");
+//   printf((char *)mystring);
+//   osoput(&mystring, "How about some pancakes?");
+//   printf((char *)mystring);
+//   osocat(&mstring, " Sure!");
+//   printf((char *)mystring);
+//   osofree(mystring);
+//
+//   > Hello World!
+//   > How about some pancakes?
+//   > How about some pancakes? Sure!
+//
+//                                 RULES
+//                                -------
+// 1. `oso *` can always be cast to `char *`, but it's your job to check if
+//    it's null before passing it to on something that doesn't tolerate null
+//    pointers.
+//
+// 2. The functions defined in this header tolerate null pointers like this:
+//
+//           `oso *` -> OK to be null.
+//    `char const *` -> Must not be null.
+//          `oso **` -> Must not be null, but the `oso *` pointed to
+//                      can be null. The pointed-to `oso *` may be
+//                      modified during the call.
+// 
+// 3. `oso *` and `char const *` as arguments to the functions here must not
+//    overlap in memory. During the call, the buffer pointed to by a `oso *`
+//    might need to be reallocated in memory to make room for the `char const
+//    *` contents, and if the `char const *` contents end up being moved by
+//    that operation, the pointer will no longer be pointing at valid memory.
+//    (This also applies to functions which accept two `oso *` as inputs.)
+//
+// Parameters with the type `oso *` are safe to pass as a null pointer. But
+// `oso **` parameters shouldn't be null. The `oso *` pointed to by the `oso
+// **` can be null, though. In other words, you need to do `&mystring` to pass
+// a `oso **` argument.
+//
+// During the function call, if the `oso *` pointed to by the `oso **` needs to
+// become non-null, or if the existing buffer needs to be moved to grow larger,
+// the `oso *` will be set to a new value.
+//
+//   oso *mystring = NULL;
+//   osolen(mystring); // Gives 0
+//   osocat(&mystring, "waffles");
+//   osolen(mystring); // Gives 7
+//   osofree(mystring);
+//
+//                                 NAMES
+//                                -------
+//   osoput______ -> Copy a string into an oso string.
+//   osocat______ -> Append a string onto the end of an oso string.
+//   ______len    -> Do it with an explicit length argument, so the C-string
+//                   doesn't have to be '\0'-terminated.
+//   ______oso    -> Do it with a second oso string.
+//   ______printf -> Do it by using printf.
+//
+//                             ALLOC FAILURE
+//                            ---------------
+// If an allocation fails (including failing to reallocate) the `oso *` will be
+// set to null. If you decide to handle memory allocation failures, you'll need
+// to check for that.
+//
+// In the oso code, if a reallocation of an existing buffer fails (`realloc()`
+// returns null) then the old, still-valid buffer is immediately freed.
+// Therefore, in an out-of-memory situation, it's possible that you will *lose*
+// your strings without getting a chance to do something with the old buffers.
+//
+// Variations of the oso functions may be added in the future, with a _c suffix
+// or something, which preserve the old buffer and return an error code in the
+// event of a reallocation failure. I'm not sure how important this is, since
+// most existing libc-based software doesn't handle out-of-memory conditions
+// for small strings without imploding.
+//
+// (sds, for example, will lose your string *and* leak the old buffer if a
+// reallocation fails.)
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute)
+#if __has_attribute(format)
+#define OSO_PRINTF(...) __attribute__((format(printf, __VA_ARGS__)))
+#endif
+#if __has_attribute(nonnull)
+#define OSO_NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
+#endif
+#endif
+#ifndef OSO_PRINTF
+#define OSO_PRINTF(...)
+#endif
+#ifndef OSO_NONNULL
+#define OSO_NONNULL(...)
+#endif
+
+typedef struct oso oso;
+
+#define osoc(s) ((char const *)s)
+
+void osoput(oso **p, char const *cstr) OSO_NONNULL();
+// ^- Copies the '\0'-terminated string into the `oso *` string located at
+//    `*p`. If `*p` is null or there isn't enough capacity to hold `cstr`, it
+//    will be reallocated. The pointer value at `*p` will be updated if
+//    necessary. `*p` and `cstr` must not point to overlapping memory.
+void osoputlen(oso **p, char const *cstr, size_t len) OSO_NONNULL();
+// ^- Same as above, but uses an additional parameter that specifies the length
+//    of `cstr, and `cstr` does not have to be '\0'-terminated.
+//    `*p` and `cstr` must not point to overlapping memory.
+void osoputoso(oso **p, oso const *other) OSO_NONNULL(1);
+// ^- Same as above, but using another `oso`. `*p` and `other` must not point
+//    to overlapping memory.
+void osoputvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2)
+    OSO_PRINTF(2, 0);
+void osoputprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2)
+    OSO_PRINTF(2, 3);
+// ^- Same as above, but do it by using printf.
+
+void osocat(oso **p, char const *cstr) OSO_NONNULL();
+void osocatlen(oso **p, char const *cstr, size_t len) OSO_NONNULL();
+void osocatoso(oso **p, oso const *other) OSO_NONNULL(1);
+void osocatvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2)
+    OSO_PRINTF(2, 0);
+void osocatprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2)
+    OSO_PRINTF(2, 3);
+// ^- Append string to oso string. Same rules as `osoput` family.
+
+void osoensurecap(oso **p, size_t cap) OSO_NONNULL();
+// ^- Ensure that s has at least `cap` memory allocated for it. This does not
+//    care about the strlen of the characters or the prefixed length count --
+//    only the backing memory allocation.
+void osomakeroomfor(oso **p, size_t len) OSO_NONNULL();
+// ^- Ensure that s has enough allocated space after the '\0'-terminnation
+//    character to hold an additional add_len characters. It does not adjust
+//    the `length` number value, only the capacity, if necessary.
+//
+//    Both `osoensurecap()` and `osomakeroomfor()` can be used to avoid making
+//    multiple smaller reallactions as the string grows in the future. You can
+//    also use them if you're going to modify the string buffer manually in
+//    your own code, and need to create some space in the buffer.
+
+void osoclear(oso **p) OSO_NONNULL();
+// ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place.
+void osofree(oso *s);
+// ^- You know. And calling with null is allowed.
+void osowipe(oso **p) OSO_NONNULL();
+// ^- It's like `osofree()`, except you give it a ptr-to-ptr, and it also sets
+//    `*p` to null for you when it's done freeing the memory.
+void ososwap(oso const **a, oso const **b) OSO_NONNULL();
+// ^- Swaps the two pointers. Yeah, that's all it does. Why? Because it's
+//    common when dealing memory management for individually allocated strings
+//    and changing between old and new string values.
+void osopokelen(oso *s, size_t len) OSO_NONNULL();
+// ^- Manually updates length field. Doesn't do anything else for you.
+
+size_t osolen(oso const *s);
+// ^- Bytes in use by the string (not including the '\0' character.)
+size_t osocap(oso const *s);
+// ^- Bytes allocated on heap (not including the '\0' terminator.)
+void osolencap(oso const *s, size_t *out_len, size_t *out_cap)
+    OSO_NONNULL(2, 3);
+// ^- Get both the len and the cap in one call.
+size_t osoavail(oso const *s);
+// ^- osocap(s) - osolen(s)
+
+void osotrim(oso *restrict s, char const *restrict cut_set) OSO_NONNULL(2);
+// ^- Remove the characters in `cut_set` from the beginning and ending of `s`.
+
+#undef OSO_PRINTF
+#undef OSO_NONNULL
--- a/thirdparty/sdd.c
+++ /dev/null
@@ -1,198 +1,0 @@
-// Derived from gingerBill's public domain gb_string.h file.
-#include "sdd.h"
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute)
-#if __has_attribute(noinline) && __has_attribute(noclone)
-#define SDD_NOINLINE __attribute__((noinline, noclone))
-#elif __has_attribute(noinline)
-#define SDD_NOINLINE __attribute__((noinline))
-#endif
-#elif defined(_MSC_VER)
-#define SDD_NOINLINE __declspec(noinline)
-#endif
-#ifndef SDD_NOINLINE
-#define SDD_NOINLINE
-#endif
-
-#define SDD_INTERNAL SDD_NOINLINE static
-#define SDD_HDR(s) ((sdd_header *)s - 1)
-#define SDD_CAP_MAX (SIZE_MAX - (sizeof(sdd_header) + 1))
-
-typedef struct sdd {
-  size_t len;
-  size_t cap;
-} sdd_header;
-
-SDD_INTERNAL sdd *sdd_impl_new(char const *init, size_t len, size_t cap) {
-  if (cap > SDD_CAP_MAX)
-    return NULL;
-  sdd_header *header = (sdd *)malloc(sizeof(sdd) + cap + 1);
-  if (!header)
-    return NULL;
-  header->len = len;
-  header->cap = cap;
-  char *str = (char *)(header + 1);
-  if (init)
-    memcpy(str, init, len);
-  str[len] = '\0';
-  return (sdd *)str;
-}
-SDD_INTERNAL sdd *sdd_impl_reallochdr(sdd_header *hdr, size_t new_cap) {
-  sdd_header *new_hdr = realloc(hdr, sizeof(sdd_header) + new_cap + 1);
-  if (!new_hdr) {
-    free(hdr);
-    return NULL;
-  }
-  new_hdr->cap = new_cap;
-  return new_hdr + 1;
-}
-SDD_INTERNAL sdd *sdd_impl_catvprintf(sdd *s, char const *fmt, va_list ap) {
-  size_t old_len;
-  int required;
-  va_list cpy;
-  va_copy(cpy, ap);
-  required = vsnprintf(NULL, 0, fmt, cpy);
-  va_end(cpy);
-  if (s) {
-    old_len = SDD_HDR(s)->len;
-    s = sdd_makeroomfor(s, (size_t)required);
-  } else {
-    old_len = 0;
-    s = sdd_newcap((size_t)required);
-  }
-  if (!s)
-    return NULL;
-  vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap);
-  SDD_HDR(s)->len = old_len + (size_t)required;
-  return s;
-}
-
-sdd *sdd_new(char const *str) {
-  size_t len = strlen(str);
-  return sdd_impl_new(str, len, len);
-}
-sdd *sdd_newlen(char const *init, size_t len) {
-  return sdd_impl_new(init, len, len);
-}
-sdd *sdd_newcap(size_t cap) { return sdd_impl_new(NULL, 0, cap); }
-sdd *sdd_dup(sdd const *str) {
-  size_t len = SDD_HDR(str)->len;
-  return sdd_impl_new((char const *)str, len, len);
-}
-sdd *sdd_newvprintf(char const *fmt, va_list ap) {
-  return sdd_impl_catvprintf(NULL, fmt, ap);
-}
-sdd *sdd_newprintf(char const *fmt, ...) {
-  sdd *s;
-  va_list ap;
-  va_start(ap, fmt);
-  s = sdd_impl_catvprintf(NULL, fmt, ap);
-  va_end(ap);
-  return s;
-}
-void sdd_free(sdd *s) {
-  if (!s)
-    return;
-  free(s - 1);
-}
-sdd *sdd_cpy(sdd *restrict s, char const *restrict cstr) {
-  return sdd_cpylen(s, cstr, strlen(cstr));
-}
-SDD_NOINLINE
-sdd *sdd_cpylen(sdd *restrict s, char const *restrict cstr, size_t len) {
-  s = sdd_ensurecap(s, len);
-  if (!s)
-    return NULL;
-  SDD_HDR(s)->len = len;
-  memcpy(s, cstr, len);
-  ((char *)s)[len] = '\0';
-  return s;
-}
-sdd *sdd_cpysdd(sdd *restrict s, sdd const *restrict other) {
-  return sdd_cpylen(s, (char const *)other, SDD_HDR(other)->len);
-}
-sdd *sdd_cat(sdd *restrict s, char const *restrict other) {
-  return sdd_catlen(s, other, strlen(other));
-}
-SDD_NOINLINE
-sdd *sdd_catlen(sdd *restrict s, char const *restrict other, size_t other_len) {
-  size_t curr_len = SDD_HDR(s)->len;
-  s = sdd_makeroomfor(s, other_len);
-  if (!s)
-    return NULL;
-  memcpy((char *)s + curr_len, other, other_len);
-  ((char *)s)[curr_len + other_len] = '\0';
-  SDD_HDR(s)->len = curr_len + other_len;
-  return s;
-}
-sdd *sdd_catsdd(sdd *restrict s, sdd const *restrict other) {
-  return sdd_catlen(s, (char const *)other, SDD_HDR(other)->len);
-}
-sdd *sdd_catvprintf(sdd *restrict s, char const *fmt, va_list ap) {
-  return sdd_impl_catvprintf(s, fmt, ap);
-}
-sdd *sdd_catprintf(sdd *restrict s, char const *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  s = sdd_impl_catvprintf(s, fmt, ap);
-  va_end(ap);
-  return s;
-}
-SDD_NOINLINE
-sdd *sdd_ensurecap(sdd *s, size_t new_cap) {
-  sdd_header *hdr = SDD_HDR(s);
-  if (new_cap > SDD_CAP_MAX) {
-    free(hdr);
-    return NULL;
-  }
-  if (hdr->cap >= new_cap)
-    return s;
-  return sdd_impl_reallochdr(hdr, new_cap);
-}
-SDD_NOINLINE
-sdd *sdd_makeroomfor(sdd *s, size_t add_len) {
-  sdd_header *hdr = SDD_HDR(s);
-  size_t len = hdr->len, cap = hdr->cap;
-  if (len > SDD_CAP_MAX - add_len) { // overflow, goodnight
-    free(hdr);
-    return NULL;
-  }
-  size_t new_cap = len + add_len;
-  if (cap >= new_cap)
-    return s;
-  return sdd_impl_reallochdr(hdr, new_cap);
-}
-size_t sdd_len(sdd const *s) { return SDD_HDR(s)->len; }
-size_t sdd_cap(sdd const *s) { return SDD_HDR(s)->cap; }
-size_t sdd_avail(sdd const *s) {
-  sdd_header *h = SDD_HDR(s);
-  return h->cap - h->len;
-}
-void sdd_clear(sdd *s) {
-  SDD_HDR(s)->len = 0;
-  ((char *)s)[0] = '\0';
-}
-void sdd_pokelen(sdd *s, size_t len) { SDD_HDR(s)->len = len; }
-void sdd_trim(sdd *restrict s, char const *restrict cut_set) {
-  char *str, *start, *end, *start_pos, *end_pos;
-  start_pos = start = str = (char *)s;
-  end_pos = end = str + SDD_HDR(s)->len - 1;
-  while (start_pos <= end && strchr(cut_set, *start_pos))
-    start_pos++;
-  while (end_pos > start_pos && strchr(cut_set, *end_pos))
-    end_pos--;
-  size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1);
-  SDD_HDR(s)->len = len;
-  if (str != start_pos)
-    memmove(str, start_pos, len);
-  str[len] = '\0';
-}
-
-#undef SDD_HDR
-#undef SDD_NOINLINE
-#undef SDD_CAP_MAX
-#undef SDD_INTERNAL
--- a/thirdparty/sdd.h
+++ /dev/null
@@ -1,104 +1,0 @@
-// Strings, Dynamic, Dumb
-#pragma once
-#include <stdarg.h>
-#include <stddef.h>
-
-#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute)
-#if __has_attribute(format)
-#define SDD_PRINTF(n1, n2) __attribute__((format(printf, n1, n2)))
-#endif
-#if __has_attribute(nonnull)
-#define SDD_NONNULL(...) __attribute__((nonnull __VA_ARGS__))
-#endif
-#if __has_attribute(malloc) && __has_attribute(warn_unused_result)
-#define SDD_ALLOC __attribute__((malloc, warn_unused_result))
-#elif __has_attribute(warn_unused_result)
-#define SDD_ALLOC __attribute__((warn_unused_result))
-#endif
-#if __has_attribute(warn_unused_result)
-#define SDD_USED __attribute__((warn_unused_result))
-#endif
-#endif
-#ifndef SDD_PRINTF
-#define SDD_PRINTF(n1, n2)
-#endif
-#ifndef SDD_NONNULL
-#define SDD_NONNULL(...)
-#endif
-#ifndef SDD_ALLOC
-#define SDD_ALLOC
-#endif
-#ifndef SDD_USED
-#define SDD_USED
-#endif
-
-typedef struct sdd sdd;
-
-#define sddc(s) ((char *)s)
-#define sddcc(s) ((char const *)s)
-
-sdd *sdd_new(char const *s) SDD_NONNULL() SDD_ALLOC;
-// ^- Create new with copy of '\0'-terminated cstring.
-sdd *sdd_newlen(char const *s, size_t len) SDD_NONNULL() SDD_ALLOC;
-// ^- Same, but without calling strlen().
-//    Resulting new string will be '\0'-terminated.
-sdd *sdd_newcap(size_t cap) SDD_ALLOC;
-// ^- 'Raw' new with a specific capacity.
-//    Length will be set to 0, and '\0' written at position 0.
-sdd *sdd_dup(sdd const *s) SDD_ALLOC;
-// ^- Same as sdd_newlen(str, sdd_len(str))
-sdd *sdd_newvprintf(char const *fmt, va_list ap) SDD_ALLOC;
-sdd *sdd_newprintf(char const *fmt, ...) SDD_PRINTF(1, 2) SDD_ALLOC;
-// ^- Create new by using printf
-void sdd_free(sdd *s);
-// ^- Calling with null is allowed.
-
-sdd *sdd_cpy(sdd *restrict s, char const *restrict cstr) SDD_NONNULL() SDD_USED;
-// ^- Set `s` to contain the contents of `cstr`. This is really more like
-//    "change into" rather than "copy".
-sdd *sdd_cpylen(sdd *restrict s, char const *restrict cstr, size_t len)
-    SDD_NONNULL() SDD_USED;
-sdd *sdd_cpysdd(sdd *restrict s, sdd const *restrict other);
-
-sdd *sdd_cat(sdd *restrict s, char const *restrict other)
-    SDD_NONNULL() SDD_USED;
-// ^- Appends contents. The two strings must not overlap in memory.
-sdd *sdd_catlen(sdd *restrict s, char const *restrict other, size_t len)
-    SDD_NONNULL() SDD_USED;
-sdd *sdd_catsdd(sdd *restrict s, sdd const *restrict other)
-    SDD_NONNULL() SDD_USED;
-sdd *sdd_catvprintf(sdd *restrict s, char const *fmt, va_list ap)
-    SDD_NONNULL((1, 2)) SDD_USED;
-sdd *sdd_catprintf(sdd *restrict s, char const *fmt, ...) SDD_NONNULL((1, 2))
-    SDD_PRINTF(2, 3) SDD_USED;
-// ^- Appends by using printf.
-
-size_t sdd_len(sdd const *s) SDD_NONNULL();
-// ^- Bytes used by string (excluding '\0' terminator)
-size_t sdd_cap(sdd const *s) SDD_NONNULL();
-// ^- Bytes allocated on heap (excluding '\0' terminator)
-size_t sdd_avail(sdd const *s) SDD_NONNULL();
-// ^- sdd_cap(s) - sdd_len(s)
-
-void sdd_clear(sdd *s) SDD_NONNULL();
-// ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place.
-void sdd_pokelen(sdd *s, size_t len) SDD_NONNULL();
-// ^- Manually update length field. Doesn't do anything else for you.
-
-void sdd_trim(sdd *restrict s, char const *cut_set) SDD_NONNULL();
-// ^- Remove the characters in cut_set from the beginning and ending of s.
-sdd *sdd_ensurecap(sdd *s, size_t cap) SDD_NONNULL() SDD_USED;
-// ^- Ensure that s has at least cap memory allocated for it. This does not
-//    care about the strlen of the characters or the prefixed length count --
-//    only the backing memory allocation.
-sdd *sdd_makeroomfor(sdd *s, size_t add_len) SDD_NONNULL() SDD_USED;
-// ^- Ensure that s has enough allocated space after the valid,
-//    null-terminated characters to hold an additional add_len characters. It
-//    does not adjust the length, only the capacity, if necessary. Soon after
-//    you call sdd_makeroomfor(), you probably will want to call sdd_pokelen(),
-//    otherwise you're probably using it incorrectly.
-
-#undef SDD_PRINTF
-#undef SDD_NONNULL
-#undef SDD_ALLOC
-#undef SDD_USED
--- a/tool
+++ b/tool
@@ -323,7 +323,7 @@
       out_exe=cli
       ;;
     orca|tui)
-      add source_files osc_out.c term_util.c sysmisc.c thirdparty/sdd.c tui_main.c
+      add source_files osc_out.c term_util.c sysmisc.c thirdparty/oso.c tui_main.c
       add cc_flags -D_XOPEN_SOURCE_EXTENDED=1
       # thirdparty headers (like sokol_time.h) should get -isystem for their
       # include dir so that any warnings they generate with our warning flags
--- a/tui_main.c
+++ b/tui_main.c
@@ -3,7 +3,7 @@
 #include "field.h"
 #include "gbuffer.h"
 #include "osc_out.h"
-#include "sdd.h"
+#include "oso.h"
 #include "sim.h"
 #include "sysmisc.h"
 #include "term_util.h"
@@ -2134,15 +2134,15 @@
   qform_push_to_nav(qf);
 }
 
-bool try_save_with_msg(Field* field, sdd const* str) {
-  if (!sdd_len(str))
+bool try_save_with_msg(Field* field, oso const* str) {
+  if (!osolen(str))
     return false;
-  bool ok = hacky_try_save(field, sddc(str));
+  bool ok = hacky_try_save(field, osoc(str));
   if (ok) {
-    qmsg_printf_push(NULL, "Saved to:\n%s", sddc(str));
+    qmsg_printf_push(NULL, "Saved to:\n%s", osoc(str));
   } else {
     qmsg_printf_push("Error Saving File", "Unable to save file to:\n%s",
-                     sddc(str));
+                     osoc(str));
   }
   return ok;
 }
@@ -2380,7 +2380,7 @@
        Argopt_portmidi_output_device},
 #endif
       {NULL, 0, NULL, 0}};
-  sdd* file_name = NULL;
+  oso* file_name = NULL;
   int undo_history_limit = 100;
   char const* osc_hostname = NULL;
   char const* osc_port = NULL;
@@ -2538,7 +2538,7 @@
 
   if (optind == argc - 1) {
     should_autosize_grid = false;
-    file_name = sdd_new(argv[optind]);
+    osoput(&file_name, argv[optind]);
   } else if (optind < argc - 1) {
     fprintf(stderr, "Expected only 1 file argument.\n");
     exit(1);
@@ -2572,18 +2572,17 @@
     }
   }
 
-  if (file_name) {
-    Field_load_error fle = field_load_file(sddc(file_name), &ged_state.field);
+  if (osolen(file_name)) {
+    Field_load_error fle = field_load_file(osoc(file_name), &ged_state.field);
     if (fle != Field_load_error_ok) {
       char const* errstr = field_load_error_string(fle);
       fprintf(stderr, "File load error: %s.\n", errstr);
       ged_deinit(&ged_state);
       qnav_deinit();
-      sdd_free(file_name);
+      osofree(file_name);
       exit(1);
     }
   } else {
-    file_name = sdd_newcap(0);
     // Temp hacky stuff: we've crammed two code paths into the KEY_RESIZE event
     // case. One of them is for the initial setup for an automatic grid size.
     // The other is for actual resize events. We will factor this out into
@@ -2598,7 +2597,7 @@
                       (Usz)init_grid_dim_x, '.');
     }
   }
-  ged_state.filename = sdd_len(file_name) ? sddc(file_name) : "unnamed";
+  ged_state.filename = osolen(file_name) ? osoc(file_name) : "unnamed";
   ged_set_midi_mode(&ged_state, &midi_mode);
 
   // Set up timer lib
@@ -2902,10 +2901,10 @@
                 push_confirm_new_file_menu();
                 break;
               case Main_menu_open:
-                push_open_form(sddc(file_name));
+                push_open_form(osoc(file_name));
                 break;
               case Main_menu_save:
-                if (sdd_len(file_name) > 0) {
+                if (osolen(file_name) > 0) {
                   try_save_with_msg(&ged_state.field, file_name);
                 } else {
                   push_save_as_form("");
@@ -2912,7 +2911,7 @@
                 }
                 break;
               case Main_menu_save_as:
-                push_save_as_form(sddc(file_name));
+                push_save_as_form(osoc(file_name));
                 break;
               case Main_menu_set_tempo:
                 push_set_tempo_form(ged_state.bpm);
@@ -2986,7 +2985,7 @@
                   ged_make_cursor_visible(&ged_state);
                   ged_state.needs_remarking = true;
                   ged_state.is_draw_dirty = true;
-                  sdd_clear(file_name);
+                  osoclear(&file_name);
                   ged_state.filename = "unnamed"; // slightly redundant
                   qnav_stack_pop();
                   pop_qnav_if_main_menu();
@@ -3022,17 +3021,17 @@
           case Qform_action_type_submitted: {
             switch (qform_id(qf)) {
             case Open_form_id: {
-              sdd* temp_name = NULL;
+              oso* temp_name = NULL;
               if (qform_get_text_line(qf, Open_name_text_line_id, &temp_name) &&
-                  sdd_len(temp_name) > 0) {
+                  osolen(temp_name) > 0) {
                 undo_history_push(&ged_state.undo_hist, &ged_state.field,
                                   ged_state.tick_num);
                 Field_load_error fle =
-                    field_load_file(sddc(temp_name), &ged_state.field);
+                    field_load_file(osoc(temp_name), &ged_state.field);
                 if (fle == Field_load_error_ok) {
                   qnav_stack_pop();
-                  file_name = sdd_cpysdd(file_name, temp_name);
-                  ged_state.filename = sddc(file_name);
+                  osoputoso(&file_name, temp_name);
+                  ged_state.filename = osoc(file_name);
                   mbuf_reusable_ensure_size(&ged_state.mbuf_r,
                                             ged_state.field.height,
                                             ged_state.field.width);
@@ -3048,43 +3047,43 @@
                   undo_history_pop(&ged_state.undo_hist, &ged_state.field,
                                    &ged_state.tick_num);
                   qmsg_printf_push("Error Loading File", "%s:\n%s",
-                                   sddc(temp_name),
+                                   osoc(temp_name),
                                    field_load_error_string(fle));
                 }
               }
-              sdd_free(temp_name);
+              osofree(temp_name);
             } break;
             case Save_as_form_id: {
-              sdd* temp_name = NULL;
+              oso* temp_name = NULL;
               if (qform_get_text_line(qf, Save_as_name_id, &temp_name) &&
-                  sdd_len(temp_name) > 0) {
+                  osolen(temp_name) > 0) {
                 qnav_stack_pop();
                 bool saved_ok = try_save_with_msg(&ged_state.field, temp_name);
                 if (saved_ok) {
-                  file_name = sdd_cpysdd(file_name, temp_name);
-                  ged_state.filename = sddc(file_name);
+                  osoputoso(&file_name, temp_name);
+                  ged_state.filename = osoc(file_name);
                 }
               }
-              sdd_free(temp_name);
+              osofree(temp_name);
             } break;
             case Set_tempo_form_id: {
-              sdd* tmpstr = NULL;
+              oso* tmpstr = NULL;
               if (qform_get_text_line(qf, Tempo_text_line_id, &tmpstr) &&
-                  sdd_len(tmpstr) > 0) {
-                int newbpm = atoi(sddc(tmpstr));
+                  osolen(tmpstr) > 0) {
+                int newbpm = atoi(osoc(tmpstr));
                 if (newbpm > 0) {
                   ged_state.bpm = (Usz)newbpm;
                   qnav_stack_pop();
                 }
               }
-              sdd_free(tmpstr);
+              osofree(tmpstr);
             } break;
             case Set_grid_dims_form_id: {
-              sdd* tmpstr = NULL;
+              oso* tmpstr = NULL;
               if (qform_get_text_line(qf, Tempo_text_line_id, &tmpstr) &&
-                  sdd_len(tmpstr) > 0) {
+                  osolen(tmpstr) > 0) {
                 int newheight, newwidth;
-                if (sscanf(sddc(tmpstr), "%dx%d", &newwidth, &newheight) == 2 &&
+                if (sscanf(osoc(tmpstr), "%dx%d", &newwidth, &newheight) == 2 &&
                     newheight > 0 && newwidth > 0 && newheight < ORCA_Y_MAX &&
                     newwidth < ORCA_X_MAX) {
                   if (ged_state.field.height != (Usz)newheight ||
@@ -3102,7 +3101,7 @@
                   qnav_stack_pop();
                 }
               }
-              sdd_free(tmpstr);
+              osofree(tmpstr);
             } break;
             }
           } break;
@@ -3170,7 +3169,7 @@
     case CTRL_PLUS('q'):
       goto quit;
     case CTRL_PLUS('o'):
-      push_open_form(sddc(file_name));
+      push_open_form(osoc(file_name));
       break;
     case KEY_UP:
     case CTRL_PLUS('k'):
@@ -3394,7 +3393,7 @@
       break;
     case CTRL_PLUS('s'):
       // TODO duplicated with menu item code
-      if (sdd_len(file_name) > 0) {
+      if (osolen(file_name) > 0) {
         try_save_with_msg(&ged_state.field, file_name);
       } else {
         push_save_as_form("");
@@ -3428,7 +3427,7 @@
   printf("\033[?2004h\n"); // Tell terminal to not use bracketed paste
   endwin();
   ged_deinit(&ged_state);
-  sdd_free(file_name);
+  osofree(file_name);
   midi_mode_deinit(&midi_mode);
 #ifdef FEAT_PORTMIDI
   if (portmidi_is_initialized)