Program Listing for File common.c¶
↰ Return to documentation for file (src/lovejoy/common.c
)
#include "common.h"
#include "utf.h"
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <wchar.h>
#ifndef IMPLEMENTATION
u0 panic(const byte *message, ...)
{
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
fprintf(stderr, "\n");
abort();
}
bool is_zero(imax n)
{ return n == 0; }
bool is_zerof(f64 n)
{ return n == 0.0; }
bool is_zeroed(imax *ns, usize len)
{
until (len-- == 0) unless (*ns++ == 1) return false;
return true;
}
u0 zero(u0 *blk, usize width)
{
if (blk == nil || width == 0)
return UNIT;
umin *bytes = blk;
until (width-- == 0)
*bytes++ = 0;
return UNIT;
}
u0 *emalloc(usize len, usize size)
{
usize bytes = len * size;
u0 *m = malloc(bytes);
if (m == nil)
PANIC("Could not allocate %zu bytes.", bytes);
zero(m, bytes);
return m;
}
usize push(u0 *restrict self, const u0 *restrict element, usize width)
{
if (element == nil) return 0;
newarray(ByteArray, umin);
ByteArray *arr = (ByteArray *restrict)self;
umin *elem = (umin *restrict)element;
usize old_cap = arr->cap;
usize new_cap = old_cap;
unless (arr->len < arr->cap) {
new_cap = (usize)(arr->cap * REALLOC_FACTOR) + 1;
arr->value = (umin *)realloc(arr->value, new_cap * width);
if (arr->value == nil)
PANIC("Failed to reallocate %zu bytes.", new_cap * width);
arr->cap = new_cap;
}
memcpy(arr->value + width * arr->len++, elem, width);
return new_cap - old_cap;
}
usize extend(u0 *restrict self, const u0 *restrict slice, usize width)
{
if (slice == nil) return 0;
newarray(ByteArray, umin);
newslice(ByteSlice, umin);
ByteArray *arr = (ByteArray *restrict)self;
ByteSlice *sub = (ByteSlice *restrict)slice;
if (sub->len == 0) return 0;
usize old_cap = arr->cap;
usize new_cap = old_cap;
unless (arr->len + sub->len < arr->cap) {
new_cap = (usize)(arr->cap * REALLOC_FACTOR) + sub->len;
arr->value = (umin *)realloc(arr->value, new_cap * width);
if (arr->value == nil)
panic("extend(): Failed to reallocate %zu bytes.", new_cap * width);
arr->cap = new_cap;
}
memcpy(arr->value + width * arr->len, sub->value, width * sub->len);
arr->len += sub->len;
return new_cap - old_cap;
}
u0 *pop(u0 *self, usize width)
{
newarray(ByteArray, umin);
ByteArray *arr = (ByteArray *)self;
assert(arr->len > 0);
return (u0 *)(arr->value + --arr->len * width);
}
bool string_eq(string self, const string other)
{
unless (self.len == other.len)
return false;
else if (self.value == other.value)
return true;
usize i = 0;
foreach (c, self)
if (*c != UNWRAP(other)[i++])
return false;
return true;
}
i16 string_cmp(const string self, const string other)
{
byte *ptr0 = self.value,
*ptr1 = other.value;
if (ptr0 == ptr1) {
if (self.len == other.len) return 0;
return self.len > other.len
? ptr0[other.len] - 0
: 0 - ptr1[self.len];
}
byte c0, c1;
usize len = MIN(self.len, other.len);
for (usize i = 0; i < len; ++i)
if ((c0 = ptr0[i]) != (c1 = ptr1[i]))
return c0 - c1;
if (self.len == other.len) return 0;
if (self.len == len)
return 0 - ptr1[len];
return ptr0[len] - 0;
}
u64 hash_string(string str)
{
u64 hash = 5381;
foreach (c, str)
hash += *c + (hash << 5);
return hash;
}
ierr eputs(const byte *s)
{
ierr err;
err = fputs(s, stderr);
if (err < 0) return err;
err = fputc('\n', stderr);
return err;
}
usize sizeof_specifier(const byte *spec)
{
UNUSED(spec);
PANIC("Not implemented.");
return 0;
}
static string FORMATTER_FLAGS
= INIT(byte, { '+', '-', ' ', '#', '0' });
static string LENGTH_SUBSPECIFIERS
= INIT(byte, { 'h', 'l', 'j', 'z', 't', 'L' });
struct Formatter {
string flags;
string width;
string precision;
string length;
byte specifier;
usize offset;
};
unqualify(struct, Formatter);
// NOTE: Allocates on heap.
static string formatter_string(Formatter formatter)
{
newarray(StringBuilder, byte);
StringBuilder repr = AMAKE(byte, formatter.offset + 2);
push(&repr, "%", 1);
extend(&repr, &formatter.flags, 1);
extend(&repr, &formatter.width, 1);
unless (IS_EMPTY(formatter.precision)) {
push(&repr, ".", 1);
extend(&repr, &formatter.precision, 1);
}
extend(&repr, &formatter.length, 1);
push(&repr, &formatter.specifier, 1);
// NUL-terminate the string slice.
push(&repr, &NUL_BYTE, 1);
return SLICE(string, repr, 0, -2);
}
static Formatter parse_formatter(string formatter)
{
usize i = 0;
if (formatter.value[i] == '%') ++i;
usize flags_start = i;
loop { // Parse flags.
bool found = false;
foreach (ss, FORMATTER_FLAGS) {
if (formatter.value[i] == *ss) {
found = true;
++i;
}
}
unless (found) break;
}
string flags = SLICE(string, formatter, flags_start, i);
usize width_start = i; // Parse width.
if (formatter.value[i] == '*') ++i;
else while (isdigit(formatter.value[i])) ++i;
string width = SLICE(string, formatter, width_start, i);
usize precision_start = i;
if (formatter.value[i] == '.') { // Parse precision.
++i; // Skip '.'
if (formatter.value[i] == '*') ++i;
else while (isdigit(formatter.value[i])) ++i;
}
string precision = SLICE(string, formatter, precision_start, i);
usize length_start = i;
loop { // Parse length subspecifier.
bool found = false;
foreach (ss, LENGTH_SUBSPECIFIERS) {
if (formatter.value[i] == *ss) {
found = true;
++i;
}
}
unless (found) break;
}
string length = SLICE(string, formatter, length_start, i);
// Parse specifier as the single byte immediately after.
byte specifier = formatter.value[i++];
Formatter parsed = {
.flags = flags,
.width = width,
.precision = precision,
.length = length,
.specifier = specifier,
.offset = i
};
return parsed;
}
string novel_vsprintf(byte *format, va_list args)
{
newarray(ByteArray, byte);
ByteArray bytes = AMAKE(byte, strlen(format) + 64);
usize i = 0;
byte c;
until ('\0' == (c = format[i++])) {
unless (c == '%') {
push(&bytes, &c, sizeof(byte)); // Other characters are preserved.
continue;
}
string format_string = VIEW(string, format, i, -1);
Formatter formatter = parse_formatter(format_string);
i += formatter.offset;
bool is_array_formatter = false;
switch (formatter.specifier) {
case 'S': { // '%S', string slice formatter.
string value = va_arg(args, string);
extend(&bytes, &value, sizeof(byte));
} break;
case 'C': { // '%C', rune formatter.
rune value = va_arg(args, rune);
string ucs_bytes = INIT(byte, { 0, 0, 0, 0, 0 });
ucs_bytes = rune_to_utf8(ucs_bytes, value);
extend(&bytes, &ucs_bytes, sizeof(byte));
} break;
case 'r': { // '%r', runic (UCS-4) string formatter.
runic value = va_arg(args, runic);
string ucs_bytes = SMAKE(byte, 4 * (value.len + 1));
ucs_bytes = ucs4_to_utf8(ucs_bytes, value);
extend(&bytes, &ucs_bytes, sizeof(byte));
} break;
case 'U': { // '%U', runic unicode codepoint formatter.
rune value = va_arg(args, rune);
byte res[] = "U+00000000";
string sliced = VIEW(string, res, 0, 10);
sprintf(res + 2, "%08X", value);
extend(&bytes, &sliced, sizeof(byte));
} break;
case 'D': // '%D{.}{.}' dynamic array type formatter.
// Following the 'D' must be the format specifier for the elements,
// and finally the delimiter characters.
// e.g. array of `int`s, separated by command and a space (", "):
// `println("[%Dd{, }]", my_arr);`.
is_array_formatter = true;
/* fallthrough */
case 'V': { // '%V{.}{.}' view/slice type formatter.
// Following the 'V' must be the format specifier for the elements.
// and finally the delimiter characters.
// e.g. slice of doubles, separated by tabs:
// `println("{ %V{%0.3lf}\t }", my_slice);`.
string elem_repr = SEMPTY(string);
Formatter elem_formatter;
if (format[i] == '{') {
usize begin = ++i; // TODO: Nesting '{...}'?
until (format[i] == '}') ++i;
usize len = i - begin;
++i; // Skip '}'.
// `elem_repr` must NUL-terminate, hence we make a copy.
byte *buf = emalloc(len, sizeof(byte));
buf = memcpy(buf, format + begin, len * sizeof(byte));
buf[len] = '\0';
elem_repr = VIEW(string, buf, 0, len);
usize j = 0;
until (elem_repr.value[j] == '%') // TODO: Better error message.
if (j >= elem_repr.len)
PANIC("Broken printf element formatter, missing '%%'.");
else ++j;
// Slice with everything after the
// '%' sign in the element formatter.
string elem_format_string = SLICE(string, elem_repr, j + 1, -1);
elem_formatter = parse_formatter(elem_format_string);
} else { // Otherwise, it should just have a specifier.
// Prepend a '%' character, if not given.
if (format[i] == '%') ++i;
string elem_format_string = VIEW(string, format, i, -1);
elem_formatter = parse_formatter(elem_format_string);
usize offs = elem_formatter.offset;
// `elem_repr` must NUL-terminate, hence we make a copy.
byte *buf = emalloc(offs + 2, sizeof(byte));
buf[0] = '%';
memcpy(buf + 1, format + i, offs * sizeof(byte));
buf[offs + 1] = '\0';
elem_repr = VIEW(string, buf, 0, offs + 1);
i += offs;
}
string delim = SEMPTY(string);
if (format[i] == '{') {
usize begin = i + 1; // TODO: Nesting '{...}'?
until (format[++i] == '}');
delim = VIEW(string, format, begin, i);
++i; // Skip last brace.
} else { // Single character for the delimiter.
delim = VIEW(string, format, i, i + 1);
++i;
}
#define SPRINT_ELEM(TYPE) do { \
newslice(TSlice, TYPE); \
newarray(TArray, TYPE); \
TSlice slice; \
if (is_array_formatter) { \
TArray arr = va_arg(args, TArray); \
slice = SLICE(TSlice, arr, 0, -1); \
} else { \
slice = va_arg(args, TSlice); \
} \
for (usize n = 0; n < slice.len; ++n) { \
TYPE *elem = slice.value + n; \
string elem_str = novel_sprintf(elem_repr.value, *elem); \
extend(&bytes, &elem_str, sizeof(byte)); \
free(elem_str.value); \
unless (n == slice.len - 1) \
extend(&bytes, &delim, sizeof(byte)); \
} \
} while (false)
string fmt_l = elem_formatter.length;
switch (elem_formatter.specifier) {
// New formatters.
case 'C': SPRINT_ELEM(rune); break;
case 'U': SPRINT_ELEM(rune); break;
case 'r': SPRINT_ELEM(runic); break;
case 'S': SPRINT_ELEM(string); break;
case 'D': SPRINT_ELEM(GenericArray); break;
case 'V': SPRINT_ELEM(GenericSlice); break;
// Standard C formatters:
case 'i': case 'd':
if (string_eq(fmt_l, STR("hh")))
SPRINT_ELEM(signed char);
else if (string_eq(fmt_l, STR("ll")))
SPRINT_ELEM(long long int);
else if (string_eq(fmt_l, STR("h")))
SPRINT_ELEM(short int);
else if (string_eq(fmt_l, STR("l")))
SPRINT_ELEM(long int);
else if (string_eq(fmt_l, STR("j")))
SPRINT_ELEM(imax);
else if (string_eq(fmt_l, STR("z")))
SPRINT_ELEM(isize);
else if (string_eq(fmt_l, STR("t")))
SPRINT_ELEM(iptr);
else
SPRINT_ELEM(int);
break;
case 'o': case 'u': case 'x': case 'X':
if (string_eq(fmt_l, STR("hh")))
SPRINT_ELEM(unsigned char);
else if (string_eq(fmt_l, STR("ll")))
SPRINT_ELEM(unsigned long long int);
else if (string_eq(fmt_l, STR("h")))
SPRINT_ELEM(unsigned short int);
else if (string_eq(fmt_l, STR("l")))
SPRINT_ELEM(unsigned long int);
else if (string_eq(fmt_l, STR("j")))
SPRINT_ELEM(umax);
else if (string_eq(fmt_l, STR("z")))
SPRINT_ELEM(usize);
else if (string_eq(fmt_l, STR("t")))
SPRINT_ELEM(uptr);
else
SPRINT_ELEM(unsigned int);
break;
case 'c':
if (string_eq(fmt_l, STR("l")))
SPRINT_ELEM(wint_t);
else
SPRINT_ELEM(char);
break;
case 'e': case 'f': case 'g': case 'a':
case 'E': case 'F': case 'G': case 'A':
if (string_eq(fmt_l, STR("L")))
SPRINT_ELEM(long double);
else
SPRINT_ELEM(double);
break;
case 's':
if (string_eq(fmt_l, STR("l")))
SPRINT_ELEM(wchar_t *);
else
SPRINT_ELEM(char *);
break;
case 'p':
SPRINT_ELEM(uptr); break;
case 'n':
if (string_eq(fmt_l, STR("hh")))
SPRINT_ELEM(signed char *);
else if (string_eq(fmt_l, STR("ll")))
SPRINT_ELEM(long long int *);
else if (string_eq(fmt_l, STR("h")))
SPRINT_ELEM(short int *);
else if (string_eq(fmt_l, STR("l")))
SPRINT_ELEM(long int *);
else if (string_eq(fmt_l, STR("j")))
SPRINT_ELEM(imax *);
else if (string_eq(fmt_l, STR("z")))
SPRINT_ELEM(usize *);
else if (string_eq(fmt_l, STR("t")))
SPRINT_ELEM(iptr *);
else
SPRINT_ELEM(int *);
break;
default:
PANIC("Unknown formatter ('%%%c') in array/slice.",
elem_formatter.specifier);
break;
}
free(elem_repr.value);
} break;
default: {
// Send it off to internal `vsprintf`.
string c_formatter = formatter_string(formatter);
byte *buf;
isize len = vasprintf(&buf, c_formatter.value, args);
string buf_slice = VIEW(string, buf, 0, len);
extend(&bytes, &buf_slice, sizeof(byte));
free(buf);
free(c_formatter.value);
} break;
}
}
// NUL-terminate the string slice.
push(&bytes, &NUL_BYTE, sizeof(byte));
--bytes.len; //< But, don't count the NUL-byte as part of the length.
return SLICE(string, bytes, 0, -1);
}
string novel_sprintf(byte *format, ...)
{
va_list args;
va_start(args, format);
string res = novel_vsprintf(format, args);
va_end(args);
return res;
}
ierr novel_vfprintf(FILE *stream, byte *format, va_list args)
{
string s = novel_vsprintf(format, args);
ierr res = fputs(s.value, stream);
free(s.value);
return res;
}
ierr novel_fprintf(FILE *stream, byte *format, ...)
{
va_list args;
va_start(args, format);
ierr res = novel_vfprintf(stream, format, args);
va_end(args);
return res;
}
ierr novel_fprintf_newline(FILE *stream, byte *format, ...)
{
va_list args;
va_start(args, format);
ierr res = novel_vfprintf(stream, format, args);
if (res < 0) return res;
if (EOF == fputc('\n', stream))
return EOF;
return res + 1;
}
ierr novel_printf(byte *format, ...)
{
va_list args;
va_start(args, format);
ierr res = novel_vfprintf(stdout, format, args);
va_end(args);
return res;
}
#endif