mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
strfromd(), strfromf(), and strfroml() are implemented directly in terms of gdtoa. If a non-conforming format string is passed, the string "EDOOFUS" is returned and errno set to EDOOFUS as an extension. Reviewed by: fuz MFC after: 1 month Pull-Request: https://github.com/freebsd/freebsd-src/pull/2301 Signed-off-by: Faraz Vahedi <[email protected]>
477 lines
9.4 KiB
C
477 lines
9.4 KiB
C
/*
|
||
* Copyright (c) 2026 Faraz Vahedi <[email protected]>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
/*
|
||
* Shared helpers for strfromd, strfromf, and strfroml (C23 §7.24.1.3).
|
||
*/
|
||
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
#include <locale.h>
|
||
#include <stdlib.h>
|
||
|
||
#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();
|
||
}
|
||
}
|