summaryrefslogtreecommitdiffstats
path: root/src/pl_string.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl_string.c')
-rw-r--r--src/pl_string.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/pl_string.c b/src/pl_string.c
new file mode 100644
index 0000000..ba25971
--- /dev/null
+++ b/src/pl_string.c
@@ -0,0 +1,418 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "hash.h"
+
+static void grow_str(void *alloc, pl_str *str, size_t len)
+{
+ // Like pl_grow, but with some extra headroom
+ if (len > pl_get_size(str->buf))
+ str->buf = pl_realloc(alloc, str->buf, len * 1.5);
+}
+
+void pl_str_append(void *alloc, pl_str *str, pl_str append)
+{
+ // Also append an extra \0 for convenience, since a lot of the time
+ // this function will be used to generate a string buffer
+ grow_str(alloc, str, str->len + append.len + 1);
+ if (append.len) {
+ memcpy(str->buf + str->len, append.buf, append.len);
+ str->len += append.len;
+ }
+ str->buf[str->len] = '\0';
+}
+
+void pl_str_append_raw(void *alloc, pl_str *str, const void *ptr, size_t size)
+{
+ if (!size)
+ return;
+ grow_str(alloc, str, str->len + size);
+ memcpy(str->buf + str->len, ptr, size);
+ str->len += size;
+}
+
+void pl_str_append_asprintf(void *alloc, pl_str *str, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ pl_str_append_vasprintf(alloc, str, fmt, ap);
+ va_end(ap);
+}
+
+void pl_str_append_vasprintf(void *alloc, pl_str *str, const char *fmt, va_list ap)
+{
+ // First, we need to determine the size that will be required for
+ // printing the entire string. Do this by making a copy of the va_list
+ // and printing it to a null buffer.
+ va_list copy;
+ va_copy(copy, ap);
+ int size = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+ if (size < 0)
+ return;
+
+ // Make room in `str` and format to there directly
+ grow_str(alloc, str, str->len + size + 1);
+ str->len += vsnprintf((char *) (str->buf + str->len), size + 1, fmt, ap);
+}
+
+int pl_str_sscanf(pl_str str, const char *fmt, ...)
+{
+ char *tmp = pl_strdup0(NULL, str);
+ va_list va;
+ va_start(va, fmt);
+ int ret = vsscanf(tmp, fmt, va);
+ va_end(va);
+ pl_free(tmp);
+ return ret;
+}
+
+int pl_strchr(pl_str str, int c)
+{
+ if (!str.len)
+ return -1;
+
+ void *pos = memchr(str.buf, c, str.len);
+ if (pos)
+ return (intptr_t) pos - (intptr_t) str.buf;
+ return -1;
+}
+
+size_t pl_strspn(pl_str str, const char *accept)
+{
+ for (size_t i = 0; i < str.len; i++) {
+ if (!strchr(accept, str.buf[i]))
+ return i;
+ }
+
+ return str.len;
+}
+
+size_t pl_strcspn(pl_str str, const char *reject)
+{
+ for (size_t i = 0; i < str.len; i++) {
+ if (strchr(reject, str.buf[i]))
+ return i;
+ }
+
+ return str.len;
+}
+
+static inline bool pl_isspace(char c)
+{
+ switch (c) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ case '\f':
+ return true;
+ default:
+ return false;
+ }
+}
+
+pl_str pl_str_strip(pl_str str)
+{
+ while (str.len && pl_isspace(str.buf[0])) {
+ str.buf++;
+ str.len--;
+ }
+ while (str.len && pl_isspace(str.buf[str.len - 1]))
+ str.len--;
+ return str;
+}
+
+int pl_str_find(pl_str haystack, pl_str needle)
+{
+ if (!needle.len)
+ return 0;
+
+ for (size_t i = 0; i + needle.len <= haystack.len; i++) {
+ if (memcmp(&haystack.buf[i], needle.buf, needle.len) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+pl_str pl_str_split_char(pl_str str, char sep, pl_str *out_rest)
+{
+ int pos = pl_strchr(str, sep);
+ if (pos < 0) {
+ if (out_rest)
+ *out_rest = (pl_str) {0};
+ return str;
+ } else {
+ if (out_rest)
+ *out_rest = pl_str_drop(str, pos + 1);
+ return pl_str_take(str, pos);
+ }
+}
+
+pl_str pl_str_split_chars(pl_str str, const char *seps, pl_str *out_rest)
+{
+ int pos = pl_strcspn(str, seps);
+ if (pos < 0) {
+ if (out_rest)
+ *out_rest = (pl_str) {0};
+ return str;
+ } else {
+ if (out_rest)
+ *out_rest = pl_str_drop(str, pos + 1);
+ return pl_str_take(str, pos);
+ }
+}
+
+pl_str pl_str_split_str(pl_str str, pl_str sep, pl_str *out_rest)
+{
+ int pos = pl_str_find(str, sep);
+ if (pos < 0) {
+ if (out_rest)
+ *out_rest = (pl_str) {0};
+ return str;
+ } else {
+ if (out_rest)
+ *out_rest = pl_str_drop(str, pos + sep.len);
+ return pl_str_take(str, pos);
+ }
+}
+
+static bool get_hexdigit(pl_str *str, int *digit)
+{
+ while (str->len && pl_isspace(str->buf[0])) {
+ str->buf++;
+ str->len--;
+ }
+
+ if (!str->len) {
+ *digit = -1; // EOF
+ return true;
+ }
+
+ char c = str->buf[0];
+ str->buf++;
+ str->len--;
+
+ if (c >= '0' && c <= '9') {
+ *digit = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ *digit = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ *digit = c - 'A' + 10;
+ } else {
+ return false; // invalid char
+ }
+
+ return true;
+}
+
+bool pl_str_decode_hex(void *alloc, pl_str hex, pl_str *out)
+{
+ if (!out)
+ return false;
+
+ uint8_t *buf = pl_alloc(alloc, hex.len / 2);
+ int len = 0;
+
+ while (hex.len) {
+ int a, b;
+ if (!get_hexdigit(&hex, &a) || !get_hexdigit(&hex, &b))
+ goto error; // invalid char
+ if (a < 0) // EOF
+ break;
+ if (b < 0) // only one digit
+ goto error;
+
+ buf[len++] = (a << 4) | b;
+ }
+
+ *out = (pl_str) { buf, len };
+ return true;
+
+error:
+ pl_free(buf);
+ return false;
+}
+
+struct pl_str_builder_t {
+ PL_ARRAY(pl_str_template) templates;
+ pl_str args;
+ pl_str output;
+};
+
+pl_str_builder pl_str_builder_alloc(void *alloc)
+{
+ pl_str_builder b = pl_zalloc_ptr(alloc, b);
+ return b;
+}
+
+void pl_str_builder_free(pl_str_builder *b)
+{
+ if (*b)
+ pl_free_ptr(b);
+}
+
+void pl_str_builder_reset(pl_str_builder b)
+{
+ *b = (struct pl_str_builder_t) {
+ .templates.elem = b->templates.elem,
+ .args.buf = b->args.buf,
+ .output.buf = b->output.buf,
+ };
+}
+
+uint64_t pl_str_builder_hash(const pl_str_builder b)
+{
+ size_t size = b->templates.num * sizeof(b->templates.elem[0]);
+ uint64_t hash = pl_mem_hash(b->templates.elem, size);
+ pl_hash_merge(&hash, pl_str_hash(b->args));
+ return hash;
+}
+
+pl_str pl_str_builder_exec(pl_str_builder b)
+{
+ pl_str args = b->args;
+
+ b->output.len = 0;
+ for (int i = 0; i < b->templates.num; i++) {
+ size_t consumed = b->templates.elem[i](b, &b->output, args.buf);
+ pl_assert(consumed <= args.len);
+ args = pl_str_drop(args, consumed);
+ }
+
+ // Terminate with an extra \0 byte for convenience
+ grow_str(b, &b->output, b->output.len + 1);
+ b->output.buf[b->output.len] = '\0';
+ return b->output;
+}
+
+void pl_str_builder_append(pl_str_builder b, pl_str_template tmpl,
+ const void *args, size_t size)
+{
+ PL_ARRAY_APPEND(b, b->templates, tmpl);
+ pl_str_append_raw(b, &b->args, args, size);
+}
+
+void pl_str_builder_concat(pl_str_builder b, const pl_str_builder append)
+{
+ PL_ARRAY_CONCAT(b, b->templates, append->templates);
+ pl_str_append_raw(b, &b->args, append->args.buf, append->args.len);
+}
+
+static size_t template_str_ptr(void *alloc, pl_str *buf, const uint8_t *args)
+{
+ const char *str;
+ memcpy(&str, args, sizeof(str));
+ pl_str_append_raw(alloc, buf, str, strlen(str));
+ return sizeof(str);
+}
+
+void pl_str_builder_const_str(pl_str_builder b, const char *str)
+{
+ pl_str_builder_append(b, template_str_ptr, &str, sizeof(str));
+}
+
+static size_t template_str(void *alloc, pl_str *buf, const uint8_t *args)
+{
+ pl_str str;
+ memcpy(&str.len, args, sizeof(str.len));
+ pl_str_append_raw(alloc, buf, args + sizeof(str.len), str.len);
+ return sizeof(str.len) + str.len;
+}
+
+void pl_str_builder_str(pl_str_builder b, const pl_str str)
+{
+ pl_str_builder_append(b, template_str, &str.len, sizeof(str.len));
+ pl_str_append_raw(b, &b->args, str.buf, str.len);
+}
+
+void pl_str_builder_printf_c(pl_str_builder b, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ pl_str_builder_vprintf_c(b, fmt, ap);
+ va_end(ap);
+}
+
+static size_t template_printf(void *alloc, pl_str *str, const uint8_t *args)
+{
+ const char *fmt;
+ memcpy(&fmt, args, sizeof(fmt));
+ args += sizeof(fmt);
+
+ return sizeof(fmt) + pl_str_append_memprintf_c(alloc, str, fmt, args);
+}
+
+void pl_str_builder_vprintf_c(pl_str_builder b, const char *fmt, va_list ap)
+{
+ pl_str_builder_append(b, template_printf, &fmt, sizeof(fmt));
+
+ // Push all of the variadic arguments directly onto `b->args`
+ for (const char *c; (c = strchr(fmt, '%')) != NULL; fmt = c + 1) {
+ c++;
+ switch (c[0]) {
+#define WRITE(T, x) pl_str_append_raw(b, &b->args, &(T) {x}, sizeof(T))
+ case '%': continue;
+ case 'c': WRITE(char, va_arg(ap, int)); break;
+ case 'd': WRITE(int, va_arg(ap, int)); break;
+ case 'u': WRITE(unsigned, va_arg(ap, unsigned)); break;
+ case 'f': WRITE(double, va_arg(ap, double)); break;
+ case 'h':
+ assert(c[1] == 'x');
+ WRITE(unsigned short, va_arg(ap, unsigned));
+ c++;
+ break;
+ case 'l':
+ assert(c[1] == 'l');
+ switch (c[2]) {
+ case 'u': WRITE(long long unsigned, va_arg(ap, long long unsigned)); break;
+ case 'd': WRITE(long long int, va_arg(ap, long long int)); break;
+ default: abort();
+ }
+ c += 2;
+ break;
+ case 'z':
+ assert(c[1] == 'u');
+ WRITE(size_t, va_arg(ap, size_t));
+ c++;
+ break;
+ case 's': {
+ pl_str str = pl_str0(va_arg(ap, const char *));
+ pl_str_append(b, &b->args, str);
+ b->args.len++; // expand to include \0 byte (from pl_str_append)
+ break;
+ }
+ case '.': {
+ assert(c[1] == '*');
+ assert(c[2] == 's');
+ int len = va_arg(ap, int);
+ const char *str = va_arg(ap, const char *);
+ WRITE(int, len);
+ pl_str_append_raw(b, &b->args, str, len);
+ c += 2;
+ break;
+ }
+ default:
+ fprintf(stderr, "Invalid conversion character: '%c'!\n", c[0]);
+ abort();
+ }
+#undef WRITE
+ }
+}