==> 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}