/* * Copyright (c) 2026 Faraz Vahedi * * SPDX-License-Identifier: BSD-2-Clause */ /* * Shared helpers for strfromd, strfromf, and strfroml (C23 §7.24.1.3). */ #include #include #include #include #include "strfrom.h" struct sf_buf { char *s; size_t n; int pos; }; /* * Write c to b->s[b->pos] if within the n-1 writable bytes; always advance * b->pos. n is the total buffer capacity, including the null slot. */ static void sf_putc(struct sf_buf *b, char c) { if (b->n > 0 && (size_t)b->pos < b->n - 1) b->s[b->pos] = c; b->pos++; } /* * Write len bytes from src. */ static void sf_write(struct sf_buf *b, const char *src, int len) { int i; for (i = 0; i < len; i++) sf_putc(b, src[i]); } /* * Write a null terminated string. */ static void sf_puts(struct sf_buf *b, const char *src) { while (*src != '\0') sf_putc(b, *src++); } /* * Write count copies of c. */ static void sf_padc(struct sf_buf *b, char c, int count) { int i; for (i = 0; i < count; i++) sf_putc(b, c); } /* * Seal the buffer: null terminate at min(pos, n-1). */ static void sf_seal(struct sf_buf *b) { if (b->n > 0) b->s[(size_t)b->pos < b->n ? b->pos : b->n - 1] = '\0'; } /* * Emit the radix. Interfaces are defined as snprintf(3) (C23 §7.24.1.3p2), * whose '.' is the LC_NUMERIC decimal point; localeconv() resolves the same * per-thread locale as vfprintf(3). The radix may be multibyte. */ static void sf_putdp(struct sf_buf *b) { sf_puts(b, localeconv()->decimal_point); } /* * Emit "[eEpP][+-]ddd": exponent sign and decimal magnitude, zero-padded to at * least mindig digits (2 for %e/%E and %g/%G, 1 for %a/%A per C23 §7.23.6.1). * * Handles the full exponent range of every supported type. */ static void sf_emit_exp(struct sf_buf *b, char ec, int exp, int mindig) { char buf[16]; int len; sf_putc(b, ec); if (exp < 0) { sf_putc(b, '-'); exp = -exp; } else { sf_putc(b, '+'); } len = 0; do { buf[len++] = '0' + exp % 10; exp /= 10; } while (exp != 0); while (len < mindig) buf[len++] = '0'; while (len > 0) sf_putc(b, buf[--len]); } /* * Parse "%[.prec]conv" per C23 §7.24.1.3. * * Returns the conversion specifier character, or '\0' with errno set to * EDOOFUS if fmt is not one of the forms the standard permits; *prec is * -1 when the precision is absent. */ char __sf_parse_fmt(const char *fmt, int *prec) { const char *p; *prec = -1; if (*fmt != '%') return ('\0'); p = fmt + 1; if (*p == '.') { *prec = 0; while (*++p >= '0' && *p <= '9') *prec = *prec * 10 + (*p - '0'); } switch (*p) { case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': break; default: return ('\0'); } return (p[1] == '\0' ? *p : '\0'); } /* * Report a format string the standard does not permit. * * Sets errno and renders "EDOOFUS" under the ordinary snprintf(3) truncation * rules, which keeps a faulty caller alive and its output obviously wrong. */ int __sf_edoofus(char *s, size_t n) { struct sf_buf b; errno = EDOOFUS; b.s = s; b.n = n; b.pos = 0; sf_puts(&b, "EDOOFUS"); sf_seal(&b); return (b.pos); } /* * Render Inf or NaN into b->s[0..n-1]. * * is_nan: non-zero if digits[0] == 'N' (NaN), zero for Infinity. */ static int sf_special(struct sf_buf *b, char conv, int signflag, int is_nan) { int upper; b->pos = 0; upper = isupper((unsigned char)conv); if (!is_nan && signflag) sf_putc(b, '-'); sf_puts(b, is_nan ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf")); sf_seal(b); return (b->pos); } /* * Render %e / %E. * * dtoa was called with mode=2, ndigits=prec+1. decpt is the position of the * first significant digit relative to the decimal point (= exponent + 1), as * returned by dtoa. */ static int sf_efmt(struct sf_buf *b, int prec, char conv, const char *digits, int ndig, int decpt, int signflag) { int avail, copy; b->pos = 0; if (signflag) sf_putc(b, '-'); /* * Leading significant digit. */ sf_putc(b, ndig > 0 ? digits[0] : '0'); if (prec > 0) { sf_putdp(b); avail = ndig > 1 ? ndig - 1 : 0; copy = avail < prec ? avail : prec; sf_write(b, digits + 1, copy); sf_padc(b, '0', prec - copy); } sf_emit_exp(b, isupper((unsigned char)conv) ? 'E' : 'e', decpt - 1, 2); sf_seal(b); return (b->pos); } /* * Render %f / %F. * * dtoa was called with mode=3, ndigits=prec. decpt gives the number of digits * before the decimal point (may be <= 0). */ static int sf_ffmt(struct sf_buf *b, int prec, int signflag, const char *digits, int ndig, int decpt) { int avail, copy, rem, zc; b->pos = 0; if (signflag) sf_putc(b, '-'); if (decpt <= 0) { sf_putc(b, '0'); if (prec > 0) { sf_putdp(b); zc = -decpt < prec ? -decpt : prec; rem = prec - zc; copy = ndig < rem ? ndig : rem; sf_padc(b, '0', zc); sf_write(b, digits, copy); sf_padc(b, '0', rem - copy); } } else { /* * decpt digits (or zeros) before the point. */ copy = ndig < decpt ? ndig : decpt; sf_write(b, digits, copy); sf_padc(b, '0', decpt - copy); if (prec > 0) { sf_putdp(b); avail = ndig - decpt; if (avail < 0) avail = 0; copy = avail < prec ? avail : prec; sf_write(b, digits + decpt, copy); sf_padc(b, '0', prec - copy); } } sf_seal(b); return (b->pos); } /* * Render %g / %G. * * dtoa was called with mode=2, ndigits=max(1, prec). */ static int sf_gfmt(struct sf_buf *b, int prec, char conv, const char *digits, int ndig, int decpt, int signflag) { int ep, copy, frac; b->pos = 0; /* * Precision 0 is treated as 1 per C23 §7.23.6.1. */ ep = prec == 0 ? 1 : prec; /* * Strip trailing zeros (no ALT flag is accepted). */ while (ndig > 1 && digits[ndig - 1] == '0') ndig--; if (signflag) sf_putc(b, '-'); /* * Standard says −4 ≤ exponent < P test: exponent = decpt − 1 */ if (decpt > -4 && decpt <= ep) { /* * %f style, per C23 §7.23.6.1. */ if (decpt <= 0) { sf_putc(b, '0'); if (ndig > 0) { sf_putdp(b); sf_padc(b, '0', -decpt); sf_write(b, digits, ndig); } } else { copy = ndig < decpt ? ndig : decpt; sf_write(b, digits, copy); sf_padc(b, '0', decpt - copy); frac = ndig - decpt; if (frac > 0) { sf_putdp(b); sf_write(b, digits + decpt, frac); } } } else { /* * %e style, per C23 §7.23.6.1. */ sf_putc(b, ndig > 0 ? digits[0] : '0'); if (ndig > 1) { sf_putdp(b); sf_write(b, digits + 1, ndig - 1); } sf_emit_exp(b, isupper((unsigned char)conv) ? 'E' : 'e', decpt - 1, 2); } sf_seal(b); return (b->pos); } /* * Render %a / %A. * * digits / ndig: hex significand digits from __hdtoa / __hldtoa; the first * digit represents the integer part of the mantissa (normally '1'). * * decpt: binary exponent such that p-exponent = decpt - 1. * * user_prec: digits after the hex point (-1 for shortest-exact). */ static int sf_afmt(struct sf_buf *b, int user_prec, char conv, const char *digits, int ndig, int decpt, int signflag) { int after, copy; b->pos = 0; if (signflag) sf_putc(b, '-'); sf_putc(b, '0'); sf_putc(b, isupper((unsigned char)conv) ? 'X' : 'x'); /* * Integer part of the significand. */ sf_putc(b, ndig > 0 ? digits[0] : '0'); after = ndig - 1; if (user_prec < 0) { /* * Shortest-exact: emit only the non-zero significant tail. */ if (after > 0) { sf_putdp(b); sf_write(b, digits + 1, after); } } else if (user_prec > 0) { sf_putdp(b); copy = after < user_prec ? after : user_prec; sf_write(b, digits + 1, copy); sf_padc(b, '0', user_prec - copy); } sf_emit_exp(b, isupper((unsigned char)conv) ? 'P' : 'p', decpt - 1, 1); sf_seal(b); return (b->pos); } /* * Hex digit table for %a / %A, selected by specifier case. */ const char * __sf_xdigits(char conv) { return (isupper((unsigned char)conv) ? "0123456789ABCDEF" : "0123456789abcdef"); } /* * Map a decimal specifier (lowercased) to its gdtoa mode and digit count. */ void __sf_decimal_mode(char lc, int prec, int *mode, int *ndig_req) { switch (lc) { case 'e': *mode = 2; *ndig_req = prec + 1; break; case 'f': *mode = 3; *ndig_req = prec; break; case 'g': *mode = 2; *ndig_req = prec == 0 ? 1 : prec; break; default: __unreachable(); } } /* * Render a finished %a / %A conversion. __hdtoa / __hldtoa always flag * Inf/NaN with decpt == INT_MAX. */ int __sf_render_hex(char *s, size_t n, char conv, int prec, char *digits, char *dend, int decpt, int signflag) { struct sf_buf b; int ndig, usprec; b.s = s; b.n = n; if (decpt == INT_MAX) return (sf_special(&b, conv, signflag, digits[0] == 'N')); ndig = (int)(dend - digits); usprec = prec >= 0 ? prec : ndig - 1; return (sf_afmt(&b, usprec, conv, digits, ndig, decpt, signflag)); } /* * Render a finished %e/%f/%g conversion. is_special is set when decpt holds * the caller's Inf/NaN sentinel (dtoa: 9999, __ldtoa: INT_MAX). */ int __sf_render_decimal(char *s, size_t n, char conv, char lc, int prec, char *digits, char *dend, int decpt, int signflag, int is_special) { struct sf_buf b; int ndig; b.s = s; b.n = n; if (is_special) return (sf_special(&b, conv, signflag, digits[0] == 'N')); ndig = (int)(dend - digits); switch (lc) { case 'e': return (sf_efmt(&b, prec, conv, digits, ndig, decpt, signflag)); case 'f': return (sf_ffmt(&b, prec, signflag, digits, ndig, decpt)); case 'g': return (sf_gfmt(&b, prec, conv, digits, ndig, decpt, signflag)); default: __unreachable(); } }