ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
strbuf.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file strbuf.h
6 * @brief Bounded no-heap string builder that fails closed on overflow (one shared copy).
7 *
8 * The same little `Buf` appender was open-coded inside the anonymous namespace of ~5
9 * codecs (utmc, sep2, openadr, atc, exc_decoder). It bump-appends into a caller-owned
10 * `char[]` and latches @c ok to false the first time something would not fit, so every
11 * later append is a no-op and callers test one flag at the end. These header-only inline
12 * helpers are the single home for it, mirroring hex.h / numparse.h - no `<stdlib.h>`,
13 * no heap, and zero link cost when unused. The verbatim pieces (struct + raw append + XML
14 * escape + decimal + JSON-string + terminate) live here; the JSON emitters (hw_health,
15 * http_delivery, ble_gatt, ...) shared them byte-for-byte, so they are no longer per-codec.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_STRBUF_H
22#define PROTOCORE_STRBUF_H
23
24#include <stddef.h>
25#include <stdint.h>
26#include <string.h>
27
28/** @brief Bump-append target; @c ok latches false once an append would overflow @c cap. */
29struct pc_sb
30{
31 char *p;
32 size_t cap;
33 size_t len;
34 bool ok;
35};
36
37/**
38 * @brief Append @p sl bytes of @p s - the primitive the others build on.
39 *
40 * Takes the length rather than finding it. Every frame here is mostly literal text whose length the
41 * compiler already knows; scanning for a NUL to rediscover it is the same waste as re-parsing a
42 * format string. Appending a literal goes through pc_sb_lit, which deduces the length.
43 */
44inline void pc_sb_put_n(pc_sb *b, const char *s, size_t sl)
45{
46 if (!b->ok)
47 {
48 return;
49 }
50 if (b->len + sl >= b->cap)
51 {
52 b->ok = false;
53 return;
54 }
55 memcpy(b->p + b->len, s, sl);
56 b->len += sl;
57}
58
59/** @brief Append NUL-terminated @p s; leaves the buffer untouched and clears @c ok if it would not fit. */
60inline void pc_sb_put(pc_sb *b, const char *s)
61{
62 if (!b->ok)
63 {
64 return;
65 }
66 pc_sb_put_n(b, s, strnlen(s, b->cap));
67}
68
69/**
70 * @brief Append a string literal. The array parameter deduces @c N, so the length is a constant.
71 *
72 * `pc_sb_put(b, "HTTP/1.1 ")` scans nine bytes at runtime to learn what the type already states.
73 * Binding to `const char (&)[N]` takes the length from the type instead, and a pointer will not
74 * bind here, so a runtime string cannot reach this overload by mistake.
75 */
76template <size_t N> inline void pc_sb_lit(pc_sb *b, const char (&s)[N])
77{
78 pc_sb_put_n(b, s, N - 1);
79}
80
81/**
82 * @brief Append as much of @p s as fits and stop, WITHOUT latching @c ok.
83 *
84 * For display text only - an `ls -l` line, a log message - where a short rendering is better than
85 * none and nothing downstream parses the result. Never use it for a protocol field: a clipped
86 * header or frame has no terminator and desynchronizes the peer, which is exactly what the
87 * latching pc_sb_put exists to prevent. The two are deliberately different functions so the choice
88 * is visible at the call site rather than being a flag someone sets once and forgets.
89 */
90inline void pc_sb_put_clip(pc_sb *b, const char *s)
91{
92 if (!b->ok || !s || b->len + 1 >= b->cap)
93 {
94 return;
95 }
96 size_t room = b->cap - b->len - 1;
97 size_t sl = strnlen(s, room);
98 memcpy(b->p + b->len, s, sl);
99 b->len += sl;
100}
101
102/**
103 * @brief Append @p v as decimal, right-aligned in at least @p columns with leading spaces, if the
104 * whole field fits - else append nothing, without latching @c ok.
105 *
106 * The display-text counterpart to pc_sb_u64. A half-written number reads as a different number, so
107 * this one is all-or-nothing where pc_sb_put_clip is byte-wise. @p columns is what aligns a
108 * fixed-width column (an `ls -l` date is `%2d` day and `%5d` year); 0 asks for the natural width.
109 * Space padding, not the zero padding pc_sb_uint does: a column pads to align, a field pads to a
110 * fixed digit count, and the two are not interchangeable on the wire.
111 */
112inline void pc_sb_u64_clip(pc_sb *b, uint64_t v, uint8_t columns)
113{
114 if (!b->ok)
115 {
116 return;
117 }
118 uint64_t probe = v;
119 size_t digits = 1;
120 while (probe >= 10)
121 {
122 probe /= 10;
123 digits++;
124 }
125 size_t width = (digits < columns) ? columns : digits;
126 if (b->len + width >= b->cap)
127 {
128 return;
129 }
130 for (size_t i = width - digits; i-- > 0;)
131 {
132 b->p[b->len + i] = ' ';
133 }
134 for (size_t i = width; i-- > width - digits;)
135 {
136 b->p[b->len + i] = (char)('0' + (unsigned)(v % 10));
137 v /= 10;
138 }
139 b->len += width;
140}
141
142/** @brief Append @p s XML-escaped (&amp; &lt; &gt; &quot;); a NULL @p s appends nothing. */
143inline void pc_sb_xml(pc_sb *b, const char *s)
144{
145 if (!b->ok || !s)
146 {
147 return;
148 }
149 for (; *s; s++)
150 {
151 const char *rep = nullptr;
152 switch (*s)
153 {
154 case '&':
155 rep = "&amp;";
156 break;
157 case '<':
158 rep = "&lt;";
159 break;
160 case '>':
161 rep = "&gt;";
162 break;
163 case '"':
164 rep = "&quot;";
165 break;
166 default:
167 break;
168 }
169 if (rep)
170 {
171 pc_sb_put(b, rep);
172 }
173 else
174 {
175 if (b->len + 1 >= b->cap)
176 {
177 b->ok = false;
178 return;
179 }
180 b->p[b->len++] = *s;
181 }
182 }
183}
184
185/** @brief Append a single character. */
186inline void pc_sb_ch(pc_sb *b, char c)
187{
188 if (!b->ok)
189 {
190 return;
191 }
192 if (b->len + 1 >= b->cap)
193 {
194 b->ok = false;
195 return;
196 }
197 b->p[b->len++] = c;
198}
199
200/**
201 * @brief Append @p v in @p base (10 or 16), left-padded with '0' to at least @p min_digits.
202 *
203 * The one shared engine behind the decimal and hex appenders: measure the field, bounds-check
204 * once, then fill it back-to-front in place. Same shape as pc_sb_u32 so neither needs a
205 * scratch array. @p min_digits is what carries a printf width like %08lx or %02d.
206 */
207inline void pc_sb_uint(pc_sb *b, uint64_t v, unsigned base, unsigned min_digits)
208{
209 if (!b->ok)
210 {
211 return;
212 }
213 // A power-of-two base is a bit field, not an arithmetic one: one hex digit IS four bits and
214 // one octal digit IS three, so extracting them is a shift and a mask. Only base 10 has to
215 // divide. Naming the width says which it is; the constants are the digit width, not a
216 // hand-tuned bit pattern.
217 const unsigned bits_per_digit = (base == 16) ? 4u : (base == 8) ? 3u : 0u;
218 const bool power_of_two = bits_per_digit != 0;
219 const uint64_t digit_mask = power_of_two ? ((1ull << bits_per_digit) - 1u) : 0u;
220
221 // Decimal on a value that fits 32 bits uses 32-bit arithmetic. The Xtensa cores here are
222 // 32-bit, so a uint64_t `/= 10` is a __udivdi3 libgcc call PER DIGIT - routing every decimal
223 // through the 64-bit engine measured slower than the snprintf it replaced (2842 vs 2632 cyc for
224 // one u32). Hex and octal never had the problem because they shift.
225 const bool narrow = !power_of_two && v <= 0xFFFFFFFFu;
226
227 uint64_t probe = v;
228 unsigned digits = 1;
229 if (power_of_two)
230 {
231 while ((probe >>= bits_per_digit) != 0)
232 {
233 digits++;
234 }
235 }
236 else if (narrow)
237 {
238 uint32_t p32 = (uint32_t)v;
239 while (p32 >= 10u)
240 {
241 p32 /= 10u;
242 digits++;
243 }
244 }
245 else
246 {
247 while (probe >= 10)
248 {
249 probe /= 10;
250 digits++;
251 }
252 }
253 if (digits < min_digits)
254 {
255 digits = min_digits;
256 }
257 if (b->len + digits >= b->cap)
258 {
259 b->ok = false;
260 return;
261 }
262 if (power_of_two)
263 {
264 for (unsigned i = digits; i-- > 0;)
265 {
266 b->p[b->len + i] = "0123456789abcdef"[v & digit_mask];
267 v >>= bits_per_digit;
268 }
269 }
270 else if (narrow)
271 {
272 uint32_t v32 = (uint32_t)v;
273 for (unsigned i = digits; i-- > 0;)
274 {
275 b->p[b->len + i] = (char)('0' + (unsigned)(v32 % 10u));
276 v32 /= 10u;
277 }
278 }
279 else
280 {
281 for (unsigned i = digits; i-- > 0;)
282 {
283 b->p[b->len + i] = (char)('0' + (unsigned)(v % 10));
284 v /= 10;
285 }
286 }
287 b->len += digits;
288}
289
290/** @brief Append @p v as decimal, zero-padded to at least @p min_digits (printf "%0Nu"). */
291inline void pc_sb_u32w(pc_sb *b, uint32_t v, unsigned min_digits)
292{
293 pc_sb_uint(b, v, 10, min_digits);
294}
295
296/** @brief Append @p v as lowercase hex, zero-padded to at least @p min_digits (printf "%0Nx"). */
297inline void pc_sb_hex(pc_sb *b, uint64_t v, unsigned min_digits)
298{
299 pc_sb_uint(b, v, 16, min_digits);
300}
301
302/** @brief Append @p v as decimal (no leading zeros; "0" for zero). */
303inline void pc_sb_u32(pc_sb *b, uint32_t v)
304{
305 pc_sb_uint(b, v, 10, 1);
306}
307
308/** @brief Append @p v as decimal (64-bit). */
309inline void pc_sb_u64(pc_sb *b, uint64_t v)
310{
311 pc_sb_uint(b, v, 10, 1);
312}
313
314/** @brief Append @p v as signed decimal (64-bit), with a leading '-' when negative. */
315inline void pc_sb_i64(pc_sb *b, int64_t v)
316{
317 // Negating INT64_MIN overflows, so the magnitude is taken through unsigned arithmetic
318 // rather than by negating the signed value.
319 uint64_t mag = (v < 0) ? (uint64_t)(-(v + 1)) + 1u : (uint64_t)v;
320 if (v < 0)
321 {
322 pc_sb_ch(b, '-');
323 }
324 pc_sb_uint(b, mag, 10, 1);
325}
326
327// 10^(2^k). Composing a power of ten from these costs at most 9 multiplies for the whole double
328// range, instead of stepping one decade at a time.
329static const double PC_POW10_BIN[9] = {1e1, 1e2, 1e4, 1e8, 1e16, 1e32, 1e64, 1e128, 1e256};
330
331/** @brief True if @p v carries the IEEE-754 sign bit, including for -0.0 (a mask, not a divide). */
332inline bool pc_signbit(double v)
333{
334 uint64_t bits;
335 memcpy(&bits, &v, sizeof(bits));
336 return (bits >> 63) != 0;
337}
338
339/** @brief True if @p v is an infinity: all exponent bits set, zero significand. */
340inline bool pc_isinf(double v)
341{
342 uint64_t bits;
343 memcpy(&bits, &v, sizeof(bits));
344 return ((bits >> 52) & 0x7FFu) == 0x7FFu && (bits & 0xFFFFFFFFFFFFFull) == 0;
345}
346
347/** @brief 10^p for p >= 0, by binary composition. */
348inline double pc_pow10i(int p)
349{
350 double r = 1.0;
351 for (int k = 0; p != 0 && k < 9; k++, p >>= 1)
352 {
353 if (p & 1)
354 {
355 r *= PC_POW10_BIN[k];
356 }
357 }
358 return r;
359}
360
361/**
362 * @brief Decimal exponent of @p v (the X such that 10^X <= |v| < 10^(X+1)), for v > 0.
363 *
364 * The binary exponent is a field of the IEEE-754 encoding, so it is a mask and a shift rather
365 * than something to converge on: multiplying it by log10(2) in fixed point (78913/2^18) gives the
366 * decimal exponent to within one, which a single comparison settles. The loop this replaces
367 * stepped one decade per iteration - about 320 of them for a denormal - to recover a number the
368 * representation was already carrying.
369 */
370inline int pc_dec_exp(double v)
371{
372 uint64_t bits;
373 memcpy(&bits, &v, sizeof(bits));
374 int be = (int)((bits >> 52) & 0x7FFu);
375 int e;
376 if (be == 0) // subnormal: no implicit leading 1, so scale into the normal range first
377 {
378 double s = v * 1e300;
379 memcpy(&bits, &s, sizeof(bits));
380 be = (int)((bits >> 52) & 0x7FFu);
381 e = (int)(((int64_t)(be - 1023) * 78913) >> 18) - 300;
382 }
383 else
384 {
385 e = (int)(((int64_t)(be - 1023) * 78913) >> 18);
386 }
387 // The estimate is exact to +/-1; settle it by comparison rather than trusting the constant.
388 double p = (e >= 0) ? pc_pow10i(e) : 1.0 / pc_pow10i(-e);
389 if (p > v)
390 {
391 e--;
392 }
393 else if (v / 10.0 >= p)
394 {
395 e++;
396 }
397 return e;
398}
399
400/**
401 * @brief Emit @p digits decimal digits of @p mant, with a '.' after the first @p point_after.
402 *
403 * Peels digits by division so no scratch array is needed. @p point_after == 0 or == @p digits
404 * emits no point.
405 */
406inline void pc_sb_digits(pc_sb *b, uint64_t mant, unsigned digits, unsigned point_after)
407{
408 uint64_t div = 1;
409 for (unsigned i = 1; i < digits; i++)
410 {
411 div *= 10;
412 }
413 for (unsigned i = 0; i < digits; i++)
414 {
415 if (i == point_after && i != 0)
416 {
417 pc_sb_ch(b, '.');
418 }
419 pc_sb_ch(b, (char)('0' + (unsigned)((mant / div) % 10)));
420 div /= 10;
421 }
422}
423
424/**
425 * @brief Append @p v with @p sig significant digits, choosing fixed or scientific form - the
426 * printf "%.<sig>g" rendering, including trailing-zero removal.
427 *
428 * Needed because %g is a wire format here, not a debug convenience: SCPI's NR2/NR3 numeric forms
429 * and SenML/JSON numbers are both defined by what this produces, so the shape has to match what
430 * the format string produced rather than being approximated with fixed decimals.
431 *
432 * Byte-identical to printf %.<sig>g for sig <= 10, which covers every call site in this library
433 * (SCPI uses 10, JSON/SenML use the default 6); verified against libc over 450k values including
434 * ties, denormals, +/-0, inf and NaN. Above that the scaling is done in double, which runs out of
435 * precision around 16 significant digits, so sig >= 15 can differ from libc in the last digit.
436 */
437inline void pc_sb_g(pc_sb *b, double v, unsigned sig)
438{
439 if (!b->ok)
440 {
441 return;
442 }
443 if (sig == 0)
444 {
445 sig = 1;
446 }
447 if (v != v) // NaN is the only value that is not equal to itself
448 {
449 pc_sb_put(b, "nan");
450 return;
451 }
452 // `v < 0` is false for -0.0, but printf emits "-0" for it. The sign is a single bit of the
453 // encoding, so it is read with a mask rather than recovered by dividing into it.
454 if (pc_signbit(v))
455 {
456 pc_sb_ch(b, '-');
457 v = -v;
458 }
459 if (pc_isinf(v))
460 {
461 pc_sb_put(b, "inf");
462 return;
463 }
464 if (v == 0.0)
465 {
466 pc_sb_ch(b, '0');
467 return;
468 }
469
470 const double v0 = v;
471 const int e0 = pc_dec_exp(v);
472 int e = e0;
473
474 uint64_t limit = 1;
475 for (unsigned i = 0; i < sig; i++)
476 {
477 limit *= 10;
478 }
479 // One scaling of the ORIGINAL value, not a round trip: multiply or divide, never both.
480 // Beyond ~1e300 the scale factor itself overflows to infinity, which turned 1e-300 into
481 // garbage ("0.000000001e-301"), so those fall back to the already-normalized mantissa. The
482 // round-trip costs tie accuracy, which is meaningless at an exponent that extreme anyway.
483 int p = (int)sig - 1 - e;
484 double scaled;
485 if (p > 300 || p < -300)
486 {
487 // The scale factor alone would overflow, so bring the value to [1,10) first and scale
488 // from there. Costs tie accuracy, which carries no meaning at an exponent this extreme.
489 double norm = (e0 >= 0) ? v0 / pc_pow10i(e0) : v0 * pc_pow10i(-e0);
490 scaled = norm * pc_pow10i((int)sig - 1);
491 }
492 else
493 {
494 double pw = pc_pow10i(p < 0 ? -p : p);
495 scaled = (p >= 0) ? v0 * pw : v0 / pw;
496 }
497
498 // printf rounds half to even (2.5 at %.1g is "2", not "3"), so a plain +0.5 is wrong on ties.
499 uint64_t mant = (uint64_t)scaled;
500 double frac = scaled - (double)mant;
501 if (frac > 0.5 || (frac == 0.5 && (mant & 1u)))
502 {
503 mant++;
504 }
505 if (mant >= limit) // the round carried into a new decade (9.9995 -> 10.000)
506 {
507 mant /= 10;
508 e++;
509 }
510 // ...and the same in the other direction. If the scaling lands just under the decade the
511 // mantissa has fewer than `sig` digits, and emitting it with the point after digit one
512 // produces a malformed "0.999...e+300" whose leading digit is zero. A mantissa is always in
513 // [1, 10), so pull it back into range and pay for it in the exponent.
514 if (sig > 1 && mant < limit / 10)
515 {
516 mant = mant * 10 + 9; // the missing digit is unknown; 9 is the value that just rounded down
517 e--;
518 }
519
520 unsigned digits = sig;
521 while (digits > 1 && mant % 10 == 0) // %g strips trailing zeros
522 {
523 mant /= 10;
524 digits--;
525 }
526
527 if (e < -4 || e >= (int)sig) // scientific, matching %g's threshold
528 {
529 pc_sb_digits(b, mant, digits, 1);
530 pc_sb_ch(b, 'e');
531 pc_sb_ch(b, e < 0 ? '-' : '+');
532 unsigned mag = (unsigned)(e < 0 ? -e : e);
533 pc_sb_u32w(b, mag, 2); // %g always emits at least two exponent digits
534 return;
535 }
536 if (e >= (int)digits - 1) // integral: all significant digits, then padding zeros
537 {
538 pc_sb_digits(b, mant, digits, 0);
539 for (int i = 0; i < e - (int)digits + 1; i++)
540 {
541 pc_sb_ch(b, '0');
542 }
543 return;
544 }
545 if (e >= 0)
546 {
547 pc_sb_digits(b, mant, digits, (unsigned)e + 1);
548 return;
549 }
550 pc_sb_put(b, "0.");
551 for (int i = 0; i < -e - 1; i++)
552 {
553 pc_sb_ch(b, '0');
554 }
555 pc_sb_digits(b, mant, digits, 0);
556}
557
558/**
559 * @brief Append @p v with exactly @p decimals digits after the point (printf "%.<decimals>f").
560 *
561 * Byte-identical to printf for |v| < 2^64, which is the range a fixed-decimal reading occupies.
562 * A larger magnitude falls back to the significant-digit form (see pc_sb_g) rather than being
563 * rendered wrong: its exact %f expansion needs big-integer arithmetic.
564 */
565inline void pc_sb_fixed(pc_sb *b, double v, unsigned decimals)
566{
567 if (!b->ok)
568 {
569 return;
570 }
571 if (v != v)
572 {
573 pc_sb_put(b, "nan");
574 return;
575 }
576 // Same negative-zero rule as pc_sb_g, read from the sign bit.
577 if (pc_signbit(v))
578 {
579 pc_sb_ch(b, '-');
580 v = -v;
581 }
582 if (pc_isinf(v))
583 {
584 pc_sb_put(b, "inf");
585 return;
586 }
587 // Beyond the 64-bit range the integer part cannot be taken through uint64 at all: casting
588 // 1e20 wrapped and rendered "0.00" for a value twenty digits long. Printing such a magnitude
589 // exactly in %f form needs big-integer arithmetic (the expansion runs to ~309 digits), which
590 // is not what a fixed-decimal appender is for, so it falls back to the significant-digit form.
591 // Truthful and obviously not a small number, rather than silently wrong.
592 if (v >= 18446744073709551616.0)
593 {
594 pc_sb_g(b, v, 10); // 10 is the precision pc_sb_g is exact to; asking for more only adds noise
595 return;
596 }
597 double scale = 1.0;
598 for (unsigned i = 0; i < decimals; i++)
599 {
600 scale *= 10.0;
601 }
602 // Split before scaling so a large magnitude does not overflow the 64-bit fraction math.
603 double ip = (double)(uint64_t)v;
604 uint64_t frac = (uint64_t)((v - ip) * scale + 0.5);
605 if (frac >= (uint64_t)scale) // the fraction rounded up into the integer part
606 {
607 ip += 1.0;
608 frac = 0;
609 }
610 pc_sb_u64(b, (uint64_t)ip);
611 if (decimals)
612 {
613 pc_sb_ch(b, '.');
614 pc_sb_u32w(b, (uint32_t)frac, decimals);
615 }
616}
617
618/** @brief Append @p s as a JSON string literal: double-quoted, with `"` and `\` backslash-escaped. A NULL
619 * @p s emits `""`. (Control chars are passed through, matching the emitters this replaced.) */
620inline void pc_sb_json(pc_sb *b, const char *s)
621{
622 pc_sb_put(b, "\"");
623 for (const char *p = s ? s : ""; *p; p++)
624 {
625 if (*p == '"' || *p == '\\')
626 {
627 if (b->len + 2 >= b->cap)
628 {
629 b->ok = false;
630 return;
631 }
632 b->p[b->len++] = '\\';
633 b->p[b->len++] = *p;
634 }
635 else if (b->len + 1 < b->cap)
636 {
637 b->p[b->len++] = *p;
638 }
639 else
640 {
641 b->ok = false;
642 }
643 }
644 pc_sb_put(b, "\"");
645}
646
647/** @brief NUL-terminate and return the built length, or 0 if the build overflowed. */
648inline size_t pc_sb_finish(pc_sb *b)
649{
650 // cap == 0 owns no bytes at all, so even the terminator is out of bounds. Every appender
651 // refuses to write into a zero-capacity buffer without latching `ok`, which left this the one
652 // path that would still have written p[0].
653 if (!b->ok || b->cap == 0)
654 {
655 return 0;
656 }
657 b->p[b->len] = '\0';
658 return b->len;
659}
660
661#endif // PROTOCORE_STRBUF_H
int pc_dec_exp(double v)
Decimal exponent of v (the X such that 10^X <= |v| < 10^(X+1)), for v > 0.
Definition strbuf.h:370
void pc_sb_g(pc_sb *b, double v, unsigned sig)
Append v with sig significant digits, choosing fixed or scientific form - the printf "%....
Definition strbuf.h:437
void pc_sb_fixed(pc_sb *b, double v, unsigned decimals)
Append v with exactly decimals digits after the point (printf "%.<decimals>f").
Definition strbuf.h:565
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_uint(pc_sb *b, uint64_t v, unsigned base, unsigned min_digits)
Append v in base (10 or 16), left-padded with '0' to at least min_digits.
Definition strbuf.h:207
void pc_sb_u64_clip(pc_sb *b, uint64_t v, uint8_t columns)
Append v as decimal, right-aligned in at least columns with leading spaces, if the whole field fits -...
Definition strbuf.h:112
void pc_sb_xml(pc_sb *b, const char *s)
Append s XML-escaped (& < > "); a NULL s appends nothing.
Definition strbuf.h:143
void pc_sb_digits(pc_sb *b, uint64_t mant, unsigned digits, unsigned point_after)
Emit digits decimal digits of mant, with a '.' after the first point_after.
Definition strbuf.h:406
void pc_sb_hex(pc_sb *b, uint64_t v, unsigned min_digits)
Append v as lowercase hex, zero-padded to at least min_digits (printf "%0Nx").
Definition strbuf.h:297
void pc_sb_put_n(pc_sb *b, const char *s, size_t sl)
Append sl bytes of s - the primitive the others build on.
Definition strbuf.h:44
bool pc_signbit(double v)
True if v carries the IEEE-754 sign bit, including for -0.0 (a mask, not a divide).
Definition strbuf.h:332
bool pc_isinf(double v)
True if v is an infinity: all exponent bits set, zero significand.
Definition strbuf.h:340
void pc_sb_ch(pc_sb *b, char c)
Append a single character.
Definition strbuf.h:186
void pc_sb_u64(pc_sb *b, uint64_t v)
Append v as decimal (64-bit).
Definition strbuf.h:309
void pc_sb_lit(pc_sb *b, const char(&s)[N])
Append a string literal. The array parameter deduces N, so the length is a constant.
Definition strbuf.h:76
void pc_sb_u32w(pc_sb *b, uint32_t v, unsigned min_digits)
Append v as decimal, zero-padded to at least min_digits (printf "%0Nu").
Definition strbuf.h:291
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
Definition strbuf.h:315
void pc_sb_put_clip(pc_sb *b, const char *s)
Append as much of s as fits and stop, WITHOUT latching ok.
Definition strbuf.h:90
void pc_sb_json(pc_sb *b, const char *s)
Append s as a JSON string literal: double-quoted, with "</tt> and <tt>\</tt> backslash-escaped....
Definition strbuf.h:620
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Definition strbuf.h:303
double pc_pow10i(int p)
10^p for p >= 0, by binary composition.
Definition strbuf.h:348
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
char * p
Definition strbuf.h:31
size_t cap
Definition strbuf.h:32
size_t len
Definition strbuf.h:33
bool ok
Definition strbuf.h:34