skip to content
==> src/text.c <==
1/* Strings, formatting, and the Rust Debug output ported messages depend on. */
2#include "mesh/text.h"
3
4#include <errno.h>
5#include <math.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <strings.h>
10
11static _Noreturn void oom(void) {
12    fputs("mesh: out of memory\n", stderr);
13    abort();
14}
15
16void *mesh_alloc(size_t n) {
17    void *p = calloc(1, n ? n : 1);
18    if (!p) oom();
19    return p;
20}
21
22void *mesh_realloc(void *p, size_t n) {
23    p = realloc(p, n ? n : 1);
24    if (!p) oom();
25    return p;
26}
27
28char *mesh_strndup(const char *s, size_t n) {
29    char *d = mesh_alloc(n + 1);
30    if (n) memcpy(d, s, n);
31    return d;
32}
33
34char *mesh_strdup(const char *s) { return mesh_strndup(s, strlen(s)); }
35
36char *mesh_vfmt(const char *fmt, va_list ap) {
37    mesh_text t = {0};
38    mesh_text_vprintf(&t, fmt, ap);
39    return mesh_text_take(&t);
40}
41
42char *mesh_fmt(const char *fmt, ...) {
43    va_list ap;
44    va_start(ap, fmt);
45    char *s = mesh_vfmt(fmt, ap);
46    va_end(ap);
47    return s;
48}
49
50static void reserve(mesh_text *t, size_t extra) {
51    if (t->len + extra + 1 <= t->cap) return;
52    size_t cap = t->cap ? t->cap : 64;
53    while (cap < t->len + extra + 1) cap *= 2;
54    t->data = mesh_realloc(t->data, cap);
55    t->cap = cap;
56}
57
58void mesh_text_putn(mesh_text *t, const char *s, size_t n) {
59    reserve(t, n);
60    if (n) memcpy(t->data + t->len, s, n);
61    t->len += n;
62    t->data[t->len] = 0;
63}
64
65void mesh_text_puts(mesh_text *t, const char *s) { mesh_text_putn(t, s, strlen(s)); }
66void mesh_text_putc(mesh_text *t, char c) { mesh_text_putn(t, &c, 1); }
67
68void mesh_text_vprintf(mesh_text *t, const char *fmt, va_list ap) {
69    va_list copy;
70    va_copy(copy, ap);
71    int n = vsnprintf(NULL, 0, fmt, copy);
72    va_end(copy);
73    if (n < 0) return;
74    reserve(t, (size_t)n);
75    vsnprintf(t->data + t->len, (size_t)n + 1, fmt, ap);
76    t->len += (size_t)n;
77}
78
79void mesh_text_printf(mesh_text *t, const char *fmt, ...) {
80    va_list ap;
81    va_start(ap, fmt);
82    mesh_text_vprintf(t, fmt, ap);
83    va_end(ap);
84}
85
86const char *mesh_text_str(mesh_text *t) { return t->data ? t->data : ""; }
87
88char *mesh_text_take(mesh_text *t) {
89    reserve(t, 0);
90    t->data[t->len] = 0;
91    char *s = t->data;
92    *t = (mesh_text){0};
93    return s;
94}
95
96void mesh_text_clear(mesh_text *t) {
97    t->len = 0;
98    if (t->data) t->data[0] = 0;
99}
100
101void mesh_text_free(mesh_text *t) {
102    free(t->data);
103    *t = (mesh_text){0};
104}
105
106bool mesh_errf(char **err, const char *fmt, ...) {
107    if (!err) return false;
108    va_list ap;
109    va_start(ap, fmt);
110    char *s = mesh_vfmt(fmt, ap);
111    va_end(ap);
112    free(*err);
113    *err = s;
114    return false;
115}
116
117void mesh_err_prefix(char **err, const char *fmt, ...) {
118    if (!err) return;
119    mesh_text t = {0};
120    va_list ap;
121    va_start(ap, fmt);
122    mesh_text_vprintf(&t, fmt, ap);
123    va_end(ap);
124    mesh_text_puts(&t, *err ? *err : "");
125    free(*err);
126    *err = mesh_text_take(&t);
127}
128
129/* One UTF-8 code point at s (n bytes left): its value and length; invalid
130 * bytes decode as themselves with length 1 (U+FFFD-free: the caller only
131 * escapes or copies). */
132static size_t utf8_decode(const unsigned char *s, size_t n, uint32_t *cp) {
133    unsigned char c = s[0];
134    if (c < 0x80) { *cp = c; return 1; }
135    size_t len = c >= 0xf0 ? 4 : c >= 0xe0 ? 3 : c >= 0xc0 ? 2 : 0;
136    if (len == 0 || len > n) { *cp = c; return 1; }
137    uint32_t v = c & (0x7f >> len);
138    for (size_t i = 1; i < len; i++) {
139        if ((s[i] & 0xc0) != 0x80) { *cp = c; return 1; }
140        v = v << 6 | (s[i] & 0x3f);
141    }
142    *cp = v;
143    return len;
144}
145
146/* Rust's escape_debug: which code points print as \u{..} (src/escapes.c). */
147extern const uint32_t mesh_text_escape_ranges[][2];
148extern const unsigned mesh_text_escape_ranges_len;
149
150static bool needs_unicode_escape(uint32_t cp) {
151    unsigned lo = 0, hi = mesh_text_escape_ranges_len;
152    while (lo < hi) {
153        unsigned mid = (lo + hi) / 2;
154        if (cp < mesh_text_escape_ranges[mid][0]) hi = mid;
155        else if (cp > mesh_text_escape_ranges[mid][1]) lo = mid + 1;
156        else return true;
157    }
158    return false;
159}
160
161void mesh_text_debug_str(mesh_text *t, const char *s, size_t n) {
162    mesh_text_putc(t, '"');
163    const unsigned char *u = (const unsigned char *)s;
164    for (size_t i = 0; i < n;) {
165        uint32_t cp;
166        size_t len = utf8_decode(u + i, n - i, &cp);
167        switch (cp) {
168        case '"': mesh_text_puts(t, "\\\""); break;
169        case '\\': mesh_text_puts(t, "\\\\"); break;
170        case '\n': mesh_text_puts(t, "\\n"); break;
171        case '\r': mesh_text_puts(t, "\\r"); break;
172        case '\t': mesh_text_puts(t, "\\t"); break;
173        case 0: mesh_text_puts(t, "\\0"); break;
174        default:
175            if (needs_unicode_escape(cp)) mesh_text_printf(t, "\\u{%x}", (unsigned)cp);
176            else mesh_text_putn(t, s + i, len);
177        }
178        i += len;
179    }
180    mesh_text_putc(t, '"');
181}
182
183char *mesh_debug_str(const char *s) {
184    mesh_text t = {0};
185    mesh_text_debug_str(&t, s, strlen(s));
186    return mesh_text_take(&t);
187}
188
189/* Shortest decimal digits that read back as v (|v| finite, > 0): the digits
190 * (no dot) and the decimal exponent of the first digit. */
191static void shortest(double v, int is_f32, char *digits, int *exp10) {
192    char buf[64];
193    for (int p = 0; p < 17; p++) {
194        snprintf(buf, sizeof buf, "%.*e", p, v);
195        double back = strtod(buf, NULL);
196        if (is_f32 ? (float)back == (float)v : back == v) break;
197    }
198    /* buf: d[.ddd]e[+-]xx */
199    char *e = strchr(buf, 'e');
200    *exp10 = atoi(e + 1);
201    size_t k = 0;
202    for (char *c = buf; c < e; c++)
203        if (*c >= '0' && *c <= '9') digits[k++] = *c;
204    while (k > 1 && digits[k - 1] == '0') k--;
205    digits[k] = 0;
206}
207
208static void debug_float(mesh_text *t, double v, int is_f32) {
209    if (isnan(v)) { mesh_text_puts(t, "NaN"); return; }
210    if (signbit(v)) mesh_text_putc(t, '-');
211    v = fabs(v);
212    if (isinf(v)) { mesh_text_puts(t, "inf"); return; }
213    if (v == 0) { mesh_text_puts(t, "0.0"); return; }
214    char d[40];
215    int e;
216    shortest(v, is_f32, d, &e);
217    size_t nd = strlen(d);
218    /* Rust's Debug: plain decimal for 1e-4 <= v < 1e16, else d[.ddd]e<exp>. */
219    if (v >= 1e16 || v < 1e-4) {
220        mesh_text_putc(t, d[0]);
221        if (nd > 1) {
222            mesh_text_putc(t, '.');
223            mesh_text_puts(t, d + 1);
224        }
225        mesh_text_printf(t, "e%d", e);
226        return;
227    }
228    if (e < 0) {
229        mesh_text_puts(t, "0.");
230        for (int i = 0; i < -e - 1; i++) mesh_text_putc(t, '0');
231        mesh_text_puts(t, d);
232    } else if ((size_t)e + 1 >= nd) {
233        mesh_text_puts(t, d);
234        for (size_t i = nd; i < (size_t)e + 1; i++) mesh_text_putc(t, '0');
235        mesh_text_puts(t, ".0");
236    } else {
237        mesh_text_putn(t, d, (size_t)e + 1);
238        mesh_text_putc(t, '.');
239        mesh_text_puts(t, d + e + 1);
240    }
241}
242
243void mesh_text_debug_f64(mesh_text *t, double v) { debug_float(t, v, 0); }
244void mesh_text_debug_f32(mesh_text *t, float v) { debug_float(t, (double)v, 1); }
245
246size_t mesh_utf8_ws_len(const char *s, size_t n) {
247    if (n == 0) return 0;
248    unsigned char c = (unsigned char)s[0];
249    if (c == ' ' || (c >= 0x09 && c <= 0x0d)) return 1;
250    if (c < 0x80) return 0;
251    uint32_t cp;
252    size_t len = utf8_decode((const unsigned char *)s, n, &cp);
253    if (len == 1) return 0;
254    bool ws = cp == 0x85 || cp == 0xa0 || cp == 0x1680 || (cp >= 0x2000 && cp <= 0x200a) || cp == 0x2028 ||
255              cp == 0x2029 || cp == 0x202f || cp == 0x205f || cp == 0x3000;
256    return ws ? len : 0;
257}
258
259void mesh_trim_start(const char **s, size_t *n) {
260    size_t k;
261    while ((k = mesh_utf8_ws_len(*s, *n)) > 0) {
262        *s += k;
263        *n -= k;
264    }
265}
266
267void mesh_trim_end(const char **s, size_t *n) {
268    while (*n > 0) {
269        /* The last code point starts at most 3 continuation bytes back. */
270        size_t start = *n - 1;
271        while (start > 0 && *n - start < 4 && ((unsigned char)(*s)[start] & 0xc0) == 0x80) start--;
272        if (mesh_utf8_ws_len(*s + start, *n - start) != *n - start) return;
273        *n = start;
274    }
275}
276
277void mesh_trim(const char **s, size_t *n) {
278    mesh_trim_start(s, n);
279    mesh_trim_end(s, n);
280}
281
282bool mesh_parse_u64(const char *s, size_t n, unsigned radix, uint64_t *out) {
283    size_t i = 0;
284    if (n > 0 && s[0] == '+') i = 1;
285    if (i == n) return false;
286    uint64_t v = 0;
287    for (; i < n; i++) {
288        unsigned c = (unsigned char)s[i], d;
289        if (c >= '0' && c <= '9') d = c - '0';
290        else if (c >= 'a' && c <= 'z') d = c - 'a' + 10;
291        else if (c >= 'A' && c <= 'Z') d = c - 'A' + 10;
292        else return false;
293        if (d >= radix) return false;
294        if (v > (UINT64_MAX - d) / radix) return false;
295        v = v * radix + d;
296    }
297    *out = v;
298    return true;
299}
300
301bool mesh_parse_u32(const char *s, size_t n, unsigned radix, uint32_t *out) {
302    uint64_t v;
303    if (!mesh_parse_u64(s, n, radix, &v) || v > UINT32_MAX) return false;
304    *out = (uint32_t)v;
305    return true;
306}
307
308static bool ieq(const char *s, size_t n, const char *w) { return strlen(w) == n && strncasecmp(s, w, n) == 0; }
309
310bool mesh_parse_f64(const char *s, size_t n, double *out) {
311    size_t i = 0;
312    bool neg = false;
313    if (i < n && (s[i] == '+' || s[i] == '-')) neg = s[i++] == '-';
314    const char *r = s + i;
315    size_t rn = n - i;
316    if (ieq(r, rn, "inf") || ieq(r, rn, "infinity")) { *out = neg ? -HUGE_VAL : HUGE_VAL; return true; }
317    if (ieq(r, rn, "nan")) { *out = NAN; return true; }
318    size_t int_digits = 0, frac_digits = 0;
319    while (i < n && s[i] >= '0' && s[i] <= '9') { i++; int_digits++; }
320    if (i < n && s[i] == '.') {
321        i++;
322        while (i < n && s[i] >= '0' && s[i] <= '9') { i++; frac_digits++; }
323    }
324    if (int_digits + frac_digits == 0) return false;
325    if (i < n && (s[i] == 'e' || s[i] == 'E')) {
326        i++;
327        if (i < n && (s[i] == '+' || s[i] == '-')) i++;
328        size_t ed = 0;
329        while (i < n && s[i] >= '0' && s[i] <= '9') { i++; ed++; }
330        if (ed == 0) return false;
331    }
332    if (i != n) return false;
333    char *tmp = mesh_strndup(s, n);
334    errno = 0;
335    *out = strtod(tmp, NULL);
336    free(tmp);
337    return true;
338}