summaryrefslogtreecommitdiff
path: root/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'util.c')
-rw-r--r--util.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..01cd517
--- /dev/null
+++ b/util.c
@@ -0,0 +1,339 @@
+#if _WIN32
+#include <intrin.h>
+#include <direct.h>
+#elif __unix__
+#include <unistd.h>
+#else
+#error "Unrecognized operating system."
+#endif
+#include <string.h>
+#include <stdlib.h>
+
+static u8 util_popcount(u64 x) {
+#ifdef __GNUC__
+ return (u8)__builtin_popcountll(x);
+#else
+ u8 count = 0;
+ while (x) {
+ x &= x-1;
+ ++count;
+ }
+ return count;
+#endif
+}
+
+static u8 util_count_leading_zeroes32(u32 x) {
+ if (x == 0) return 32; // GCC's __builtin_clz is undefined for x = 0
+#if __GNUC__ && UINT_MAX == 4294967295
+ return (u8)__builtin_clz(x);
+#elif _WIN32 && UINT_MAX == 4294967295
+ return (u8)__lzcnt(x);
+#else
+ u8 count = 0;
+ for (int i = 31; i >= 0; --i) {
+ if (x & ((u32)1<<i)) {
+ break;
+ }
+ ++count;
+ }
+ return count;
+#endif
+}
+
+static bool util_is_power_of_2(u64 x) {
+ return util_popcount(x) == 1;
+}
+
+// for finding a character in a char32 string
+static char32_t *util_mem32chr(char32_t *s, char32_t c, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
+ if (s[i] == c) {
+ return &s[i];
+ }
+ }
+ return NULL;
+}
+
+static char32_t const *util_mem32chr_const(char32_t const *s, char32_t c, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
+ if (s[i] == c) {
+ return &s[i];
+ }
+ }
+ return NULL;
+}
+
+static bool str_is_prefix(char const *str, char const *prefix) {
+ return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+static bool streq(char const *a, char const *b) {
+ return strcmp(a, b) == 0;
+}
+
+// duplicates at most n characters from src
+static char *strn_dup(char const *src, size_t n) {
+ size_t len = strlen(src);
+ if (n > len)
+ n = len;
+ char *ret = malloc(n + 1);
+ if (ret) {
+ memcpy(ret, src, n);
+ ret[n] = 0;
+ }
+ return ret;
+}
+
+// duplicates a null-terminated string. the returned string should be passed to free()
+static char *str_dup(char const *src) {
+ return strn_dup(src, SIZE_MAX);
+}
+
+// like snprintf, but not screwed up on windows
+#define str_printf(str, size, ...) (str)[(size) - 1] = '\0', snprintf((str), (size) - 1, __VA_ARGS__)
+// like snprintf, but the size is taken to be the length of the array str.
+// first, check that str is actually an array
+#define strbuf_printf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \
+ str_printf(str, sizeof str, __VA_ARGS__)
+#define str_catf(str, size, ...) str_printf((str) + strlen(str), (size) - strlen(str), __VA_ARGS__)
+#define strbuf_catf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \
+ str_catf(str, sizeof str, __VA_ARGS__)
+
+// on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823
+// it is unusual to have a string that long.
+#define STRLEN_SAFE_MAX (UINT_MAX >> 2)
+
+// safer version of strncat. dst_sz includes a null terminator.
+static void strn_cat(char *dst, size_t dst_sz, char const *src, size_t src_len) {
+ size_t dst_len = strlen(dst);
+
+ // make sure dst_len + src_len + 1 doesn't overflow
+ if (dst_len > STRLEN_SAFE_MAX || src_len > STRLEN_SAFE_MAX) {
+ assert(0);
+ return;
+ }
+
+ if (dst_len >= dst_sz) {
+ // dst doesn't actually contain a null-terminated string!
+ assert(0);
+ return;
+ }
+
+ if (dst_len + src_len + 1 > dst_sz) {
+ // number of bytes left in dst, not including null terminator
+ size_t n = dst_sz - dst_len - 1;
+ memcpy(dst + dst_len, src, n);
+ dst[dst_sz - 1] = 0; // dst_len + n == dst_sz - 1
+ } else {
+ memcpy(dst + dst_len, src, src_len);
+ dst[dst_len + src_len] = 0;
+ }
+}
+
+// safer version of strcat. dst_sz includes a null terminator.
+static void str_cat(char *dst, size_t dst_sz, char const *src) {
+ strn_cat(dst, dst_sz, src, strlen(src));
+}
+
+// safer version of strncpy. dst_sz includes a null terminator.
+static void str_cpy(char *dst, size_t dst_sz, char const *src) {
+ size_t srclen = strlen(src);
+ size_t n = srclen; // number of bytes to copy
+
+ if (dst_sz == 0) {
+ assert(0);
+ return;
+ }
+
+ if (dst_sz-1 < n)
+ n = dst_sz-1;
+ memcpy(dst, src, n);
+ dst[n] = 0;
+}
+
+#define strbuf_cpy(dst, src) str_cpy(dst, sizeof dst, src)
+
+// advances str to the start of the next UTF8 character
+static void utf8_next_char_const(char const **str) {
+ if (**str) {
+ do {
+ ++*str;
+ } while (((u8)(**str) & 0xC0) == 0x80); // while we are on a continuation byte
+ }
+}
+
+static void print_bytes(u8 *bytes, size_t n) {
+ u8 *b, *end;
+ for (b = bytes, end = bytes + n; b != end; ++b)
+ printf("%x ", *b);
+ printf("\n");
+}
+
+/*
+does this predicate hold for all the characters of s. predicate is int (*)(int) instead
+of bool (*)(char) so that you can pass isprint, etc. to it.
+*/
+static bool str_satisfies(char const *s, int (*predicate)(int)) {
+ char const *p;
+ for (p = s; *p; ++p)
+ if (!predicate(*p))
+ return false;
+ return true;
+}
+
+
+static int strcmp_case_insensitive(char const *a, char const *b) {
+#if _WIN32
+ return _stricmp(a, b);
+#else
+ return strcasecmp(a, b);
+#endif
+}
+
+// function to be passed into qsort for case insensitive sorting
+static int str_qsort_case_insensitive_cmp(const void *av, const void *bv) {
+ char const *const *a = av, *const *b = bv;
+ return strcmp_case_insensitive(*a, *b);
+}
+
+static void *qsort_ctx_arg;
+static int (*qsort_ctx_cmp)(void *, const void *, const void *);
+static int qsort_with_context_cmp(const void *a, const void *b) {
+ return qsort_ctx_cmp(qsort_ctx_arg, a, b);
+}
+
+static void qsort_with_context(void *base, size_t nmemb, size_t size, int (*compar)(void *, const void *, const void *), void *arg) {
+ // @TODO(eventually): write this yourself
+ // just use global variables. hopefully we don't try to run this in something multithreaded!
+ qsort_ctx_arg = arg;
+ qsort_ctx_cmp = compar;
+ qsort(base, nmemb, size, qsort_with_context_cmp);
+}
+
+// the actual file name part of the path; get rid of the containing directory.
+// NOTE: the returned string is part of path, so you don't need to free it or anything.
+static char const *path_filename(char const *path) {
+ char const *last_path_sep = strrchr(path, PATH_SEPARATOR);
+ if (last_path_sep)
+ return last_path_sep + 1;
+ // (a relative path with no path separators)
+ return path;
+}
+
+static bool path_is_absolute(char const *path) {
+ return path[0] == PATH_SEPARATOR
+ #if _WIN32
+ || path[1] == ':'
+ #endif
+ ;
+}
+
+// assuming `dir` is an absolute path, returns the absolute path of `relpath`, relative to `dir`.
+static void path_full(char const *dir, char const *relpath, char *abspath, size_t abspath_size) {
+ assert(abspath_size);
+ if (path_is_absolute(relpath)) {
+ if (strchr(ALL_PATH_SEPARATORS, relpath[0])) {
+ // make sure that on windows, if dir's drive is C: the absolute path of \a is c:\a
+ abspath[0] = '\0';
+ strn_cat(abspath, abspath_size, dir, strcspn(dir, ALL_PATH_SEPARATORS));
+ str_cat(abspath, abspath_size, relpath);
+ } else {
+ str_cpy(abspath, abspath_size, relpath);
+ }
+ return;
+ }
+ str_cpy(abspath, abspath_size, dir);
+
+ while (1) {
+ size_t component_len = strcspn(relpath, ALL_PATH_SEPARATORS);
+ char const *component_end = relpath + component_len;
+
+ size_t len = strlen(abspath);
+ if (component_len == 1 && relpath[0] == '.') {
+ // ., do nothing
+ } else if (component_len == 2 && relpath[0] == '.' && relpath[1] == '.') {
+ // ..
+ char *lastsep = strrchr(abspath, PATH_SEPARATOR);
+ if (lastsep == abspath)
+ lastsep[1] = '\0';
+ else
+ lastsep[0] = '\0';
+ } else {
+ if (abspath[len - 1] != PATH_SEPARATOR)
+ str_cat(abspath, abspath_size, PATH_SEPARATOR_STR);
+ strn_cat(abspath, abspath_size, relpath, component_len);
+ }
+ if (*component_end == 0)
+ break;
+ else
+ relpath = component_end + 1;
+ }
+}
+
+// returns true if the paths are the same.
+// handles the fact that paths are case insensitive on windows.
+// treats links as different from the files they point to.
+static bool paths_eq(char const *path1, char const *path2) {
+#if _WIN32
+ return _stricmp(path1, path2) == 0;
+#else
+ return streq(path1, path2);
+#endif
+}
+
+static void change_directory(char const *path) {
+#if _WIN32
+ _chdir(path);
+#else
+ chdir(path);
+#endif
+}
+
+// returns true on success
+static bool copy_file(char const *src, char const *dst) {
+ bool success = false;
+ FILE *src_file = fopen(src, "rb");
+ if (src_file) {
+ FILE *dst_file = fopen(dst, "wb");
+ if (dst_file) {
+ char buf[1024];
+ while (1) {
+ size_t count = fread(buf, 1, sizeof buf, src_file);
+ fwrite(buf, 1, count, dst_file);
+ if (count < sizeof buf) break;
+ }
+ success = !ferror(src_file) && !ferror(dst_file);
+ fclose(dst_file);
+ }
+ fclose(src_file);
+ }
+ return success;
+}
+
+// Read the binary contents of the given file as a null-terminated string.
+// The returned string should be freed.
+// Returns NULL on error.
+static char *read_file_contents(char const *filename) {
+ FILE *file = fopen(filename, "rb");
+ if (file) {
+ fseek(file, 0, SEEK_END);
+ size_t file_size = (size_t)ftell(file);
+ fseek(file, 0, SEEK_SET);
+ char *contents = malloc(file_size + 1);
+ if (contents) {
+ size_t bytes_read = fread(contents, 1, file_size, file);
+ if (bytes_read == file_size) {
+ contents[file_size] = '\0';
+ return contents;
+ } else {
+ // read error
+ free(contents);
+ contents = NULL;
+ }
+ }
+ fclose(file);
+ return contents;
+ } else {
+ return NULL;
+ }
+}