+/* ez-string.c: String builder/allocator/iterator.
+
+ Copyright (C) 2024 Michael Zucchi
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <errno.h>
+
+#include "ez-list.h"
+#include "ez-string.h"
+
+#define D(x)
+
+/*
+ Fixed
+ Accesses an existing buffer for reading or writing. Addresses are persistant.
+
+ Grow
+ A buffer is allocated and re-allocated as necessary for writing. Addresses
+ are not persistant. Can be used for reading.
+
+ Chunk
+ A series of buffers are allocated as necessary. Addresses are persistant.
+ Can only be used in write-only mode.
+
+ */
+
+static void ez_string_init(struct ez_string * __restrict s, const struct ez_string_class *class) {
+ s->class = (struct ez_string_class *)class;
+ s->pos = NULL;
+ s->end = NULL;
+ s->data = NULL;
+ s->error = 0;
+ ez_list_init(&s->hunks);
+}
+
+/* ********************************************************************** */
+/* fixed size input/output */
+
+static void *fixed_reserve(struct ez_string * __restrict s, size_t size) {
+ // write out
+ // error? flush?
+ return NULL;
+}
+
+static void *fixed_take(struct ez_string * __restrict s, size_t size) {
+ // error? fill?
+ return NULL;
+}
+
+static void fixed_truncate(struct ez_string * __restrict s, void *p) {
+}
+
+const struct ez_string_class fixed_class = {
+ fixed_reserve,
+ fixed_take,
+ fixed_truncate,
+};
+
+void ez_string_init_fixed(struct ez_string * __restrict s, char *data, size_t size) {
+ ez_string_init(s, &fixed_class);
+ s->pos = s->data = data;
+ s->end = data + size;
+}
+
+/* ********************************************************************** */
+/* grow mode - a single buffer is grown as necessary */
+
+static void *grow_reserve(struct ez_string * __restrict s, size_t size) {
+ size_t pos = s->data ? s->pos - s->data : 0;
+ size_t new = s->data ? s->end - s->data : EZ_STRING_HUNK_SIZE;
+ char *data;
+
+ while (new - pos < size)
+ new = new + (new >> 1);
+
+ data = realloc(s->data, sizeof(struct ez_hunk) + new);
+ if (data) {
+ s->data = data;
+ s->end = data + new;
+ s->pos = data + pos + size;
+
+ return data + pos;
+ } else {
+ s->error = errno;
+ return NULL;
+ }
+}
+
+static void *grow_take(struct ez_string * __restrict s, size_t size) {
+ // Error
+ return NULL;
+}
+
+static void grow_truncate(struct ez_string * __restrict s, void *p) {
+ if ((char *)p < s->end && (char *)p >= s->data) {
+ s->pos = p;
+ } else {
+ // error?
+ }
+}
+
+const struct ez_string_class grow_class = {
+ grow_reserve,
+ grow_take,
+ grow_truncate,
+};
+
+void ez_string_init_grow(struct ez_string * __restrict s) {
+ ez_string_init(s, &grow_class);
+}
+
+/* ********************************************************************** */
+/* basically an obstack - growable in steps and addresses static */
+
+static void *chunk_reserve(struct ez_string * __restrict s, size_t size) {
+ size_t new = size <= EZ_STRING_HUNK_THRESHOLD ? EZ_STRING_HUNK_SIZE : size;
+ size_t keep = 0;
+ struct ez_hunk *h, *t;
+
+ t = ez_list_tail(&s->hunks);
+ if (t->ln.pred) {
+ keep = s->pos - t->pos;
+ new += keep;
+ while (new < size)
+ new = new + (new >> 1);
+ if (t->pos == t->data) {
+ //printf("chunk grow %zd -> %zd\n", keep, new);
+ ez_node_remove(t);
+ h = realloc(t, sizeof(struct ez_hunk) + new);
+ if (!h) {
+ ez_list_addtail(&s->hunks, t);
+ s->error = ENOMEM;
+ return NULL;
+ }
+ goto init;
+ }
+
+ //printf("chunk save %zd\n", t->pos - t->data);
+ }
+
+ //printf("chunk append %zd + keep=%zd + new=%zd\n", new, keep, size);
+
+ h = malloc(sizeof(struct ez_hunk) + new + keep);
+ if (!h) {
+ s->error = ENOMEM;
+ return NULL;
+ }
+
+ if (keep)
+ memcpy(h->data, t->pos, keep);
+
+init:
+ ez_list_addtail(&s->hunks, h);
+
+ h->pos = h->data;
+ s->pos = h->data + size + keep;
+ s->data = h->data;
+ s->end = h->end = h->data + new;
+
+ return h->data + keep;
+}
+
+static void *chunk_take(struct ez_string * __restrict s, size_t size) {
+ // error? fill?
+ return NULL;
+}
+
+static void chunk_truncate(struct ez_string * __restrict s, void *p) {
+}
+
+const struct ez_string_class chunk_class = {
+ chunk_reserve,
+ chunk_take,
+ chunk_truncate,
+};
+
+void ez_string_init_chunk(struct ez_string * __restrict s) {
+ ez_string_init(s, &chunk_class);
+}
+
+/* free all the hunks, should it leave one behind? */
+void ez_string_chunk_reset(struct ez_string * __restrict s) {
+ D(printf("%p: reset\n", s));
+ for (struct ez_hunk *w = ez_list_head(&s->hunks), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w)) {
+ D(printf("%p: free hunk %p - %p\n", w, w->data, w->end));
+ free(w);
+ }
+ ez_string_init_chunk(s);
+}
+
+size_t ez_string_chunk_size(struct ez_string * __restrict s) {
+ struct ez_hunk *t = ez_list_tail(&s->hunks);
+
+ return t == (struct ez_hunk *)&s->hunks ? 0 : s->pos - t->pos;
+}
+
+void *ez_string_chunk_end(struct ez_string * __restrict s, size_t *size) {
+ struct ez_hunk *h = ez_list_tail(&s->hunks);
+
+ //if (h->ln.pred) {
+ if (h != (struct ez_hunk *)(&s->hunks)) {
+ void *mem = h->pos;
+
+ *size = s->pos - h->pos;
+ h->pos = s->pos;
+
+ return mem;
+ } else
+ return NULL;
+}
+
+void *ez_string_chunk_end0(struct ez_string * __restrict s) {
+ size_t size;
+
+ ez_string_putc(s, 0);
+
+ return ez_string_chunk_end(s, &size);
+}
+
+void *ez_string_chunk_alloc(struct ez_string * __restrict s, size_t size) {
+ size_t sizep;
+
+ ez_string_reserve(s, size);
+
+ return ez_string_chunk_end(s, &sizep);
+}
+
+// output
+
+#ifndef EZ_STRING_INLINE
+void *ez_string_reserve(struct ez_string * __restrict s, size_t size) {
+ char *pos = s->pos;
+ char *new = pos + size;
+
+ if (new < pos) {
+ s->error = EINVAL;
+ return NULL;
+ }
+
+ if (new <= s->end) {
+ s->pos = new;
+ return pos;
+ } else {
+ return s->class->reserve(s, size);
+ }
+}
+#endif
+
+void *ez_string_put(struct ez_string * __restrict s, const void *src, size_t size) {
+ void *dst = ez_string_reserve(s, size);
+
+ if (dst)
+ memcpy(dst, src, size);
+
+ return dst;
+}
+
+void *ez_string_putc(struct ez_string * __restrict s, char c) {
+ char *pos = s->pos;
+ if (pos < s->end)
+ *s->pos++ = c;
+ else {
+ pos = s->class->reserve(s, 1);
+ if (pos)
+ *pos = c;
+ }
+ return pos;
+}
+
+int ez_string_print(struct ez_string * __restrict s, const char *src) {
+ char *dst = ez_string_put(s, src, strlen(src));
+
+ return dst != NULL ? 0 : -1;
+}
+
+static int inline bitflagged(int c) {
+ return (1 << (c - ' '));
+}
+
+static int inline isflagged(int x, int c) {
+ return (x & (1 << (c - ' '))) != 0;
+}
+
+static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+typedef struct udiv_t {
+ uint32_t quot, rem;
+} udiv_t;
+
+#define HAVE_DIVMOD
+
+#if defined(HAVE_DIVMOD)
+udiv_t udiv10(uint32_t num) {
+ return (udiv_t){ num / 10, num % 10 };
+}
+#elif defined(HAVE_MULH)
+// ESP32C3 has mulh, this is a bit faster
+udiv_t udiv10(uint32_t num) {
+ uint32_t q = (uint64_t)num * 0x19999999 >> 32;
+ uint32_t r = num - q * 10;
+
+ if (r >= 10) {
+ r -= 10;
+ q += 1;
+ }
+
+ return (udiv_t){ q, r };
+}
+#else
+// ESP8266 doesn't have mulh
+udiv_t udiv10(uint32_t num) {
+ uint16_t ah = num >> 16;
+ uint16_t al = num;
+ uint16_t bh = 0x1999;
+ uint16_t bl = 0x9999;
+
+ uint32_t c = ah * bh;
+ uint32_t d = ah * bl;
+ uint32_t e = al * bh;
+ uint32_t f = al * bl;
+
+ uint32_t t = d + e + (f >> 16);
+
+ uint32_t q = c + (t >> 16);
+ uint32_t r = num - q * 10;
+
+ if (r >= 10) {
+ r -= 10;
+ q += 1;
+ }
+
+ return (udiv_t){ q, r };
+}
+#endif
+
+//#pragma GCC diagnostic push
+//#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+
+/*
+ what interface for this?
+*/
+int ez_string_printf(struct ez_string * __restrict iob, const char *fmt, ...) {
+ va_list ap;
+ char c;
+
+ va_start(ap, fmt);
+ while ((c = *fmt++)) {
+ if (c != '%') {
+ fchar:
+ ez_string_putc(iob, c);
+ continue;
+ }
+ c = *fmt++;
+ if (c == '%')
+ goto fchar;
+
+ const char *s = "";
+ int slen = 0;
+ int width = 0;
+ int flag = 0;
+ uint32_t u;
+ int32_t d;
+#ifdef EZ_STRING_FORMAT64
+ uint64_t U;
+ int64_t D;
+#endif
+ char pad = ' ';
+ int lpad = 0, rpad = 0;
+ char work[32];
+ char *w = work + sizeof(work);
+ char sign = '+';
+
+ while (c >= ' ' && c <= '0') {
+ flag |= bitflagged(c);
+ c = *fmt++;
+ }
+ while (c >= '0' && c <= '9') {
+ width = width * 10 + (c - '0');
+ c = *fmt++;
+ }
+
+ switch (c) {
+ case 0:
+ // truncated % sequence
+ goto done;
+ case 'd':
+ d = va_arg(ap, int32_t);
+ if (d < 0) {
+ sign = '-';
+ flag |= bitflagged('+');
+ u = -d;
+ } else {
+ u = d;
+ }
+ goto fdec;
+ case 'u':
+ u = va_arg(ap, uint32_t);
+ fdec:
+ do {
+ udiv_t r = udiv10(u);
+ *--w = hex[r.rem];
+ u = r.quot;
+ } while (u > 0);
+
+ if (isflagged(flag, '0') & !isflagged(flag, '-')) {
+ pad = '0';
+ if (isflagged(flag, '+')) {
+ width -= 1;
+ ez_string_putc(iob, sign);
+ }
+ } else {
+ if (isflagged(flag, '+'))
+ *--w = sign;
+ }
+
+ s = w;
+ slen = work + sizeof(work) - w;
+ break;
+ case 'x':
+ u = va_arg(ap, uint32_t);
+ do {
+ *--w = hex[u & 0xf];
+ u >>= 4;
+ } while (u > 0);
+
+ if (isflagged(flag, '0'))
+ pad = '0';
+
+ s = w;
+ slen = work + sizeof(work) - w;
+ break;
+#ifdef EZ_STRING_FORMAT64
+ case 'X': // 64-bit versions?
+ U = va_arg(ap, uint64_t);
+ u = U >> 32;
+ do {
+ *--w = hex[u & 0xf];
+ u >>= 4;
+ } while (u > 0);
+ u = U;
+ goto fhex;
+ case 'D':
+ D = va_arg(ap, int64_t);
+ if (D < 0) {
+ sign = '-';
+ U = -D;
+ } else {
+ U = D;
+ }
+ goto fdec64;
+ case 'U':
+ U = va_arg(ap, uint64_t);
+ fdec64:
+ while (U >> 16) {
+ *--w = (U % 10) + '0';
+ U / = 10;
+ }
+ u = U;
+ goto fdec;
+#endif
+ case 's':
+ s = va_arg(ap, const char *);
+ slen = strlen(s);
+ break;
+ }
+
+ if (width > slen) {
+ if (isflagged(flag, '-'))
+ rpad = width - slen;
+ else
+ lpad = width - slen;
+ }
+ while (lpad--)
+ ez_string_putc(iob, pad);
+ ez_string_put(iob, s, slen);
+ while (rpad--)
+ ez_string_putc(iob, pad);
+ }
+done:
+ va_end(ap);
+
+ return 0;
+}
+//#pragma GCC diagnostic pop
+
+
+// input
+#ifndef EZ_STRING_INLINE
+void *ez_string_take(struct ez_string * __restrict s, size_t size) {
+ char *pos = s->pos;
+ char *new = pos + size;
+
+ if (new < pos) {
+ s->error = EINVAL;
+ return NULL;
+ }
+
+ if (new <= s->end) {
+ s->pos = new;
+ return pos;
+ } else {
+ return s->class->take(s, size);
+ }
+}
+#endif
+
+int ez_string_get(struct ez_string * __restrict s, char *dst, size_t len) {
+ size_t max = s->end - s->pos;
+
+ if (max < len)
+ len = max;
+ memcpy(dst, ez_string_take(s, len), len);
+ return len;
+}
+
+int ez_string_getc(struct ez_string * __restrict s) {
+ if (s->pos < s->end) {
+ return *s->pos++;
+ } else {
+ char *p = s->class->take(s, 1);
+ return p ? *(unsigned char *)p : -1;
+ }
+}