Browse Source

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.
master
cancel 5 years ago
parent
commit
14c8610315
  1. 9
      term_util.c
  2. 4
      term_util.h
  3. 232
      thirdparty/oso.c
  4. 182
      thirdparty/oso.h
  5. 198
      thirdparty/sdd.c
  6. 104
      thirdparty/sdd.h
  7. 2
      tool
  8. 79
      tui_main.c

9
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,6 +564,7 @@ int qform_id(Qform const* qf) { return qf->id; }
void qform_add_text_line(Qform* qf, int id, char const* initial) {
FIELD* f = new_field(1, 30, 0, 0, 0, 0);
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);
@ -665,7 +666,7 @@ FIELD* qform_find_field(Qform const* qf, int id) {
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 @@ bool qform_get_text_line(Qform const* qf, int id, sdd** out) {
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;
}

4
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_add_text_line(Qform* qf, int id, char const* initial);
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;

232
thirdparty/oso.c

@ -0,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

182
thirdparty/oso.h

@ -0,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

198
thirdparty/sdd.c

@ -1,198 +0,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

104
thirdparty/sdd.h

@ -1,104 +0,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

2
tool

@ -323,7 +323,7 @@ build_target() {
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

79
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 @@ void push_open_form(char const* initial) {
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
}
}
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 @@ int main(int argc, char** argv) {
(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,17 +2901,17 @@ int main(int argc, char** argv) {
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("");
}
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
qnav_stack_pop();
}
}
sdd_free(tmpstr);
osofree(tmpstr);
} break;
}
} break;
@ -3170,7 +3169,7 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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 @@ quit:
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)

Loading…
Cancel
Save