cosmopolitan/libc/stdio/fmt.c
Jōshin 6e6fc38935
Apply clang-format update to repo (#1154)
Commit bc6c183 introduced a bunch of discrepancies between what files
look like in the repo and what clang-format says they should look like.
However, there were already a few discrepancies prior to that. Most of
these discrepancies seemed to be unintentional, but a few of them were
load-bearing (e.g., a #include that violated header ordering needing
something to have been #defined by a 'later' #include.)

I opted to take what I hope is a relatively smooth-brained approach: I
reverted the .clang-format change, ran clang-format on the whole repo,
reapplied the .clang-format change, reran clang-format again, and then
reverted the commit that contained the first run. Thus the full effect
of this PR should only be to apply the changed formatting rules to the
repo, and from skimming the results, this seems to be the case.

My work can be checked by applying the short, manual commits, and then
rerunning the command listed in the autogenerated commits (those whose
messages I have prefixed auto:) and seeing if your results agree.

It might be that the other diffs should be fixed at some point but I'm
leaving that aside for now.

fd '\.c(c|pp)?$' --print0| xargs -0 clang-format -i
2024-04-25 10:38:00 -07:00

1477 lines
40 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2021 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
│ │
│ Copyright (C) 1997, 1999, 2001 Lucent Technologies │
│ All Rights Reserved │
│ │
│ Permission to use, copy, modify, and distribute this software and │
│ its documentation for any purpose and without fee is hereby │
│ granted, provided that the above copyright notice appear in all │
│ copies and that both that the copyright notice and this │
│ permission notice and warranty disclaimer appear in supporting │
│ documentation, and that the name of Lucent or any of its entities │
│ not be used in advertising or publicity pertaining to │
│ distribution of the software without specific, written prior │
│ permission. │
│ │
│ LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, │
│ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. │
│ IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY │
│ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES │
│ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER │
│ IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, │
│ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF │
│ THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/divmod10.internal.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/bsr.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/mem/reverse.internal.h"
#include "libc/runtime/internal.h"
#include "libc/serialize.h"
#include "libc/str/str.h"
#include "libc/str/strwidth.h"
#include "libc/str/tab.internal.h"
#include "libc/str/thompike.h"
#include "libc/str/unicode.h"
#include "libc/str/utf16.h"
#include "libc/sysv/errfuns.h"
#include "third_party/gdtoa/gdtoa.h"
#define BUFFER_SIZE 144
#define PRINTF_NTOA_BUFFER_SIZE 144
#define FLAGS_ZEROPAD 0x01
#define FLAGS_LEFT 0x02
#define FLAGS_PLUS 0x04
#define FLAGS_SPACE 0x08
#define FLAGS_HASH 0x10
#define FLAGS_PRECISION 0x20
#define FLAGS_ISSIGNED 0x40
#define FLAGS_NOQUOTE 0x80
#define FLAGS_QUOTE FLAGS_SPACE
#define FLAGS_GROUPING FLAGS_NOQUOTE
#define FLAGS_REPR FLAGS_PLUS
#define __FMT_PUT(C) \
do { \
char Buf[1] = {C}; \
if (out(Buf, arg, 1) == -1) { \
return -1; \
} \
} while (0)
struct FPBits {
uint32_t bits[4];
const FPI *fpi;
int sign;
int ex; // exponent
int kind;
};
union U {
double d;
uint64_t q;
long double ld;
uint32_t ui[4];
uint16_t us[5];
};
static const FPI kFpiDbl = {
.nbits = DBL_MANT_DIG,
.emin = 3 - DBL_MAX_EXP - DBL_MANT_DIG,
.emax = DBL_MAX_EXP - DBL_MANT_DIG,
.rounding = FPI_Round_near,
.sudden_underflow = 0,
};
static const FPI kFpiLdbl = {
.nbits = LDBL_MANT_DIG,
.emin = 3 - LDBL_MAX_EXP - LDBL_MANT_DIG,
.emax = LDBL_MAX_EXP - LDBL_MANT_DIG,
.rounding = FPI_Round_near,
.sudden_underflow = 0,
};
static const char kSpecialFloats[2][2][4] = {
{"INF", "inf"},
{"NAN", "nan"},
};
typedef int (*out_f)(const char *, void *, size_t);
typedef int (*emit_f)(out_f, void *, uint64_t);
uint128_t __udivmodti4(uint128_t, uint128_t, uint128_t *);
static int __fmt_atoi(const char **str) {
int i;
for (i = 0; '0' <= **str && **str <= '9'; ++*str) {
i *= 10;
i += **str - '0';
}
return i;
}
static int __fmt_pad(int out(const char *, void *, size_t), void *arg,
unsigned long n) {
int i, rc;
for (rc = i = 0; i < n; ++i)
rc |= out(" ", arg, 1);
return rc;
}
static int __fmt_ntoa_format(int out(const char *, void *, size_t), void *arg,
char *buf, unsigned len, bool negative,
unsigned log2base, unsigned prec, unsigned width,
unsigned char flags, const char *alphabet) {
unsigned i, prec_width_zeros;
char alternate_form_middle_char, sign_character;
unsigned actual_buf_len;
actual_buf_len = len;
prec_width_zeros = 0;
/* pad leading zeros */
if (width && (flags & FLAGS_ZEROPAD) &&
(negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
if (len < prec) {
prec_width_zeros += (prec - len);
len = prec;
}
if ((flags & FLAGS_ZEROPAD) && (len < width)) {
prec_width_zeros += (width - len);
len = width;
}
/* handle hash */
if (flags & FLAGS_HASH) {
if ((!(flags & FLAGS_PRECISION) || log2base == 3) && len &&
((len >= prec) || (len >= width)) &&
(prec_width_zeros || buf[len - 1] == '0')) {
if (prec_width_zeros) {
--prec_width_zeros;
}
--len;
if (len < actual_buf_len) {
actual_buf_len = len;
}
if (len && (log2base == 4 || log2base == 1) &&
(prec_width_zeros || buf[len - 1] == '0')) {
if (prec_width_zeros) {
--prec_width_zeros;
}
--len;
if (len < actual_buf_len) {
actual_buf_len = len;
}
}
}
alternate_form_middle_char = '\0';
if ((log2base == 4 || log2base == 1)) {
++len;
alternate_form_middle_char =
alphabet[17]; // x, X or b (for the corresponding conversion
// specifiers)
}
++len;
}
sign_character = '\0';
if (negative) {
++len;
sign_character = '-';
} else if (flags & FLAGS_PLUS) {
++len;
sign_character = '+'; /* ignore the space if the '+' exists */
} else if (flags & FLAGS_SPACE) {
++len;
sign_character = ' ';
}
/* pad spaces up to given width */
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
if (len < width) {
if (__fmt_pad(out, arg, width - len) == -1)
return -1;
}
}
if (sign_character != '\0' && out(&sign_character, arg, 1) == -1)
return -1;
if (flags & FLAGS_HASH) {
if (out("0", arg, 1) == -1)
return -1;
if (alternate_form_middle_char != '\0' &&
out(&alternate_form_middle_char, arg, 1) == -1)
return -1;
}
for (i = 0; i < prec_width_zeros; ++i)
if (out("0", arg, 1) == -1)
return -1;
reverse(buf, actual_buf_len);
if (out(buf, arg, actual_buf_len) == -1)
return -1;
/* append pad spaces up to given width */
if (flags & FLAGS_LEFT) {
if (len < width) {
if (__fmt_pad(out, arg, width - len) == -1)
return -1;
}
}
return 0;
}
static int __fmt_ntoa2(int out(const char *, void *, size_t), void *arg,
uint128_t value, bool neg, unsigned log2base,
unsigned prec, unsigned width, unsigned flags,
const char *alphabet) {
uint128_t remainder;
unsigned len, count, digit;
char buf[BUFFER_SIZE];
len = 0;
// we check for log2base!=3, since otherwise we'll print nothing for
// a value of 0 with precision 0 when # mandates that one be printed
if (!value && log2base != 3)
flags &= ~FLAGS_HASH;
if (value || !(flags & FLAGS_PRECISION)) {
count = 0;
do {
if (!log2base) {
if (value <= UINT64_MAX) {
value = __divmod10(value, &digit);
} else {
value = __udivmodti4(value, 10, &remainder);
digit = remainder;
}
} else {
digit = value;
digit &= (1u << log2base) - 1;
value >>= log2base;
}
if ((flags & FLAGS_GROUPING) && count == 3) {
buf[len++] = ',';
count = 1;
} else {
count++;
}
buf[len++] = alphabet[digit];
} while (value);
npassert(count <= BUFFER_SIZE);
}
return __fmt_ntoa_format(out, arg, buf, len, neg, log2base, prec, width,
flags, alphabet);
}
static int __fmt_ntoa(int out(const char *, void *, size_t), void *arg,
uint128_t value, unsigned char signbit,
unsigned long log2base, unsigned long prec,
unsigned long width, unsigned char flags,
const char *lang) {
bool neg;
uint128_t sign;
/* ignore '0' flag when prec or minus flag is given */
if (flags & (FLAGS_PRECISION | FLAGS_LEFT)) {
flags &= ~FLAGS_ZEROPAD;
}
/* no plus / space flag for u, x, X, o, b */
if (!(flags & FLAGS_ISSIGNED)) {
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
}
neg = 0;
sign = 1;
sign <<= signbit;
value &= sign | (sign - 1);
if (flags & FLAGS_ISSIGNED) {
if (value != sign) {
if (value & sign) {
value = ~value + 1;
value &= sign | (sign - 1);
neg = 1;
}
value &= sign - 1;
} else {
neg = 1;
}
}
return __fmt_ntoa2(out, arg, value, neg, log2base, prec, width, flags, lang);
}
/**
* Converts byte to word-encoded C string literal representation.
*/
static int __fmt_cescapec(int c) {
switch ((c &= 255)) {
case '\a':
return '\\' | 'a' << 8;
case '\b':
return '\\' | 'b' << 8;
case '\t':
return '\\' | 't' << 8;
case '\n':
return '\\' | 'n' << 8;
case '\v':
return '\\' | 'v' << 8;
case '\f':
return '\\' | 'f' << 8;
case '\r':
return '\\' | 'r' << 8;
case '"':
return '\\' | '"' << 8;
case '\'':
return '\\' | '\'' << 8;
case '\\':
return '\\' | '\\' << 8;
default:
if (' ' <= c && c <= '~') {
return c;
} else {
return '\\' | //
('0' + (c >> 6)) << 8 | //
('0' + ((c >> 3) & 7)) << 16 | //
('0' + (c & 7)) << 24;
}
}
}
static int __fmt_stoa_byte(out_f out, void *a, uint64_t c) {
char buf[1] = {c};
return out(buf, a, 1);
}
static int __fmt_stoa_wide(out_f out, void *a, uint64_t w) {
char buf[8];
if (!isascii(w))
w = tpenc(w);
WRITE64LE(buf, w);
return out(buf, a, w ? (bsr(w) >> 3) + 1 : 1);
}
static int __fmt_stoa_bing(out_f out, void *a, uint64_t w) {
char buf[8];
w = tpenc(kCp437[w & 0xFF]);
WRITE64LE(buf, w);
return out(buf, a, w ? (bsr(w) >> 3) + 1 : 1);
}
static int __fmt_stoa_quoted(out_f out, void *a, uint64_t w) {
char buf[8];
if (isascii(w)) {
w = __fmt_cescapec(w);
} else {
w = tpenc(w);
}
WRITE64LE(buf, w);
return out(buf, a, w ? (bsr(w) >> 3) + 1 : 1);
}
/**
* Converts string to array.
*
* This is used by __fmt() to implement the %s and %c directives. The
* content outputted to the array is always UTF-8, but the input may be
* UTF-16 or UTF-32.
*
* @see __fmt()
*/
static int __fmt_stoa(int out(const char *, void *, size_t), void *arg,
void *data, unsigned long flags, unsigned long precision,
unsigned long width, unsigned char signbit,
unsigned char qchar) {
wint_t wc;
unsigned n;
emit_f emit;
char *p, buf[1];
unsigned w, pad;
bool justdobytes, ignorenul;
p = data;
if (!p) {
p = ((flags & FLAGS_REPR) ? "NULL" : "(null)");
signbit = 0;
flags |= FLAGS_NOQUOTE;
if (flags & FLAGS_PRECISION) {
precision = min(strlen(p), precision);
}
}
ignorenul = false;
justdobytes = false;
if (signbit == 15 || signbit == 63) {
if (flags & FLAGS_QUOTE) {
emit = __fmt_stoa_quoted;
ignorenul = flags & FLAGS_PRECISION;
} else {
emit = __fmt_stoa_wide;
}
} else if (flags & FLAGS_HASH) {
justdobytes = true;
emit = __fmt_stoa_bing;
ignorenul = flags & FLAGS_PRECISION;
} else if (flags & FLAGS_QUOTE) {
emit = __fmt_stoa_quoted;
ignorenul = flags & FLAGS_PRECISION;
} else {
justdobytes = true;
emit = __fmt_stoa_byte;
}
if (!(flags & FLAGS_PRECISION))
precision = -1;
if (!(flags & FLAGS_PRECISION) || !ignorenul) {
if (signbit == 63) {
precision = wcsnlen((const wchar_t *)p, precision);
} else if (signbit == 15) {
precision = strnlen16((const char16_t *)p, precision);
} else {
precision = strnlen(p, precision);
}
}
pad = 0;
if (width) {
w = precision;
if (signbit == 63) {
w = wcsnwidth((const wchar_t *)p, precision, 0);
} else if (signbit == 15) {
w = strnwidth16((const char16_t *)p, precision, 0);
} else {
w = strnwidth(p, precision, 0);
}
if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) {
w += 2 + (signbit == 63) + (signbit == 15);
}
if (w < width) {
pad = width - w;
}
}
if (pad && !(flags & FLAGS_LEFT)) {
if (__fmt_pad(out, arg, pad) == -1)
return -1;
}
if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) {
if (signbit == 63) {
if (out("L", arg, 1) == -1)
return -1;
} else if (signbit == 15) {
if (out("u", arg, 1) == -1)
return -1;
}
buf[0] = qchar;
if (out(buf, arg, 1) == -1)
return -1;
}
if (justdobytes) {
while (precision--) {
wc = *p++ & 0xff;
if (!wc && !ignorenul)
break;
if (emit(out, arg, wc) == -1)
return -1;
}
} else {
while (precision--) {
if (signbit == 15) {
wc = *(const char16_t *)p;
if (!wc && !ignorenul)
break;
if (IsUcs2(wc)) {
p += sizeof(char16_t);
} else if (IsUtf16Cont(wc)) {
p += sizeof(char16_t);
continue;
} else if (!precision) {
break;
} else {
--precision;
wc = MergeUtf16(wc, *(const char16_t *)p);
}
} else if (signbit == 63) {
wc = *(const wint_t *)p;
if (!wc && !ignorenul)
break;
p += sizeof(wint_t);
if (!wc)
break;
} else {
wc = *p++ & 0xff;
if (!wc && !ignorenul)
break;
if (!isascii(wc)) {
if (ThomPikeCont(wc))
continue;
n = ThomPikeLen(wc) - 1;
wc = ThomPikeByte(wc);
if (n > precision)
break;
precision -= n;
while (n--) {
wc = ThomPikeMerge(wc, *p++);
}
}
}
if (emit(out, arg, wc) == -1)
return -1;
}
}
if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) {
buf[0] = qchar;
if (out(buf, arg, 1) == -1)
return -1;
}
if (pad && (flags & FLAGS_LEFT)) {
if (__fmt_pad(out, arg, pad) == -1)
return -1;
}
return 0;
}
static void __fmt_dfpbits(union U *u, struct FPBits *b) {
int ex, i;
b->fpi = &kFpiDbl;
b->sign = u->ui[1] & 0x80000000L;
b->bits[1] = u->ui[1] & 0xfffff;
b->bits[0] = u->ui[0];
if ((ex = (u->ui[1] & 0x7ff00000L) >> 20) != 0) {
if (ex != 0x7ff) {
i = STRTOG_Normal;
b->bits[1] |= 1 << (52 - 32); // set lowest exponent bit
} else if (b->bits[0] | b->bits[1]) {
i = STRTOG_NaN;
} else {
i = STRTOG_Infinite;
}
} else if (b->bits[0] | b->bits[1]) {
i = STRTOG_Denormal;
ex = 1;
} else {
i = STRTOG_Zero;
}
b->kind = i;
b->ex = ex - (0x3ff + 52);
}
static void __fmt_ldfpbits(union U *u, struct FPBits *b) {
#if LDBL_MANT_DIG == 53
__fmt_dfpbits(u, b);
#else
int ex, i;
uint16_t sex;
#if LDBL_MANT_DIG == 64
b->bits[3] = 0;
b->bits[2] = 0;
b->bits[1] = ((unsigned)u->us[3] << 16) | u->us[2];
b->bits[0] = ((unsigned)u->us[1] << 16) | u->us[0];
sex = u->us[4];
#elif LDBL_MANT_DIG == 113
b->bits[3] = u->ui[3] & 0xffff;
b->bits[2] = u->ui[2];
b->bits[1] = u->ui[1];
b->bits[0] = u->ui[0];
sex = u->ui[3] >> 16;
#else
#error "unsupported architecture"
#endif
b->fpi = &kFpiLdbl;
b->sign = sex & 0x8000;
if ((ex = sex & 0x7fff) != 0) {
if (ex != 0x7fff) {
i = STRTOG_Normal;
#if LDBL_MANT_DIG == 113
b->bits[3] |= 1 << (112 - 32 * 3); // set lowest exponent bit
#endif
} else if (b->bits[0] | b->bits[1] | b->bits[2] | b->bits[3]) {
i = STRTOG_NaN;
} else {
i = STRTOG_Infinite;
}
} else if (b->bits[0] | b->bits[1] | b->bits[2] | b->bits[3]) {
i = STRTOG_Denormal;
ex = 1;
} else {
i = STRTOG_Zero;
}
b->kind = i;
b->ex = ex - (0x3fff + (LDBL_MANT_DIG - 1));
#endif
}
// returns number of hex digits minus 1, or 0 for zero
static int __fmt_fpiprec(struct FPBits *b) {
const FPI *fpi;
int i, j, k, m;
uint32_t *bits;
if (b->kind == STRTOG_Zero)
return (b->ex = 0);
fpi = b->fpi;
bits = b->bits;
for (k = (fpi->nbits - 1) >> 2; k > 0; --k) {
if ((bits[k >> 3] >> 4 * (k & 7)) & 0xf) {
m = k >> 3;
for (i = 0; i <= m; ++i)
if (bits[i]) {
if (i > 0) {
k -= 8 * i;
b->ex += 32 * i;
for (j = i; j <= m; ++j) {
bits[j - i] = bits[j];
}
}
break;
}
for (i = 0; i < 28 && !((bits[0] >> i) & 0xf); i += 4)
donothing;
if (i) {
b->ex += i;
m = k >> 3;
k -= (i >> 2);
for (j = 0;; ++j) {
bits[j] >>= i;
if (j == m)
break;
bits[j] |= bits[j + 1] << (32 - i);
}
}
break;
}
}
return k;
}
// round to prec hex digits after the "."
// prec1 = incoming precision (after ".")
static int __fmt_bround(struct FPBits *b, int prec, int prec1) {
uint32_t *bits, t;
int i, inc, j, k, m, n;
m = prec1 - prec;
bits = b->bits;
inc = 0;
k = m - 1;
if ((t = bits[k >> 3] >> (j = (k & 7) * 4)) & 8) {
if (t & 7)
goto inc1;
if (j && bits[k >> 3] << (32 - j))
goto inc1;
while (k >= 8) {
k -= 8;
if (bits[k >> 3]) {
inc1:
inc = 1;
goto haveinc;
}
}
}
haveinc:
b->ex += m * 4;
i = m >> 3;
k = prec1 >> 3;
j = i;
if ((n = 4 * (m & 7)))
for (;; ++j) {
bits[j - i] = bits[j] >> n;
if (j == k)
break;
bits[j - i] |= bits[j + 1] << (32 - n);
}
else
for (;; ++j) {
bits[j - i] = bits[j];
if (j == k)
break;
}
k = prec >> 3;
if (inc) {
for (j = 0; !(++bits[j] & 0xffffffff); ++j)
donothing;
if (j > k) {
onebit:
bits[0] = 1;
b->ex += 4 * prec;
return 1;
}
if ((j = prec & 7) < 7 && bits[k] >> (j + 1) * 4)
goto onebit;
}
for (i = 0; !(bits[i >> 3] & (0xf << 4 * (i & 7))); ++i)
donothing;
if (i) {
b->ex += 4 * i;
prec -= i;
j = i >> 3;
i &= 7;
i *= 4;
for (m = j;; ++m) {
bits[m - j] = bits[m] >> i;
if (m == k)
break;
bits[m - j] |= bits[m + 1] << (32 - i);
}
}
return prec;
}
static int __fmt_noop(const char *, void *, size_t) {
return 0;
}
/**
* Implements {,v}{,s{,n},{,{,x}as},f,d}printf domain-specific language.
*
* Type Specifiers
*
* - `%s` char * (thompson-pike unicode)
* - `%ls` wchar_t * (32-bit unicode → thompson-pike unicode)
* - `%hs` char16_t * (16-bit unicode → thompson-pike unicode)
* - `%b` int (radix 2 binary)
* - `%o` int (radix 8 octal)
* - `%d` int (radix 10 decimal)
* - `%x` int (radix 16 hexadecimal)
* - `%X` int (radix 16 hexadecimal uppercase)
* - `%p` pointer (48-bit hexadecimal)
* - `%u` unsigned
* - `%g` double (smart formatting)
* - `%e` double (expo formatting)
* - `%f` double (ugly formatting)
* - `%a` double (hex formatting)
*
* Size Modifiers
*
* - `%hhd` char (8-bit)
* - `%hd` short (16-bit)
* - `%ld` long (64-bit)
* - `%lu` unsigned long (64-bit)
* - `%lx` unsigned long (64-bit hexadecimal)
* - `%jd` intmax_t (64-bit)
* - `%jjd` int128_t (128-bit)
* - `%Lg` long double
*
* Width Modifiers
*
* - `%08d` fixed columns w/ zero leftpadding
* - `%8d` fixed columns w/ space leftpadding
* - `%*s` variable column string (thompson-pike)
*
* Precision Modifiers
*
* - `%.8s` supplied byte length (obeys nul terminator)
* - `%.*s` supplied byte length argument (obeys nul terminator)
* - ``%`.*s`` supplied byte length argument c escaped (ignores nul term)
* - `%#.*s` supplied byte length argument visualized (ignores nul term)
* - `%.*hs` supplied char16_t length argument (obeys nul terminator)
* - `%.*ls` supplied wchar_t length argument (obeys nul terminator)
*
* Formatting Modifiers
*
* - `%,d` thousands separators
* - `%'s` escaped c string literal
* - ``%`c`` c escaped character
* - ``%`'c`` c escaped character quoted
* - ``%`s`` c escaped string
* - ``%`'s`` c escaped string quoted
* - ``%`s`` escaped double quoted c string literal
* - ``%`c`` escaped double quoted c character literal
* - `%+d` plus leftpad if positive (aligns w/ negatives)
* - `% d` space leftpad if positive (aligns w/ negatives)
* - `%#s` datum (radix 256 null-terminated ibm cp437)
* - `%#x` int (radix 16 hexadecimal w/ 0x prefix if not zero)
*
* This implements most of ANSI C's printf floating-point directives,
* omitting L, with %.0g and %.0G giving the shortest decimal string
* that rounds to the number being converted, and with negative
* precisions allowed for %f.
*
* @note implementation detail of printf(), snprintf(), etc.
* @see printf() for wordier documentation
* @note netlib.org is so helpful
* @asyncsignalsafe if floating point isn't used
* @vforksafe if floating point isn't used
*/
int __fmt(void *fn, void *arg, const char *format, va_list va) {
long ld;
void *p;
double x;
unsigned u;
union U un;
char ibuf[21];
bool longdouble;
unsigned long lu;
struct FPBits fpb;
wchar_t charbuf[1];
const char *alphabet;
unsigned char signbit, log2base;
int c, k, i1, bw, rc, bex, prec1, decpt;
int (*out)(const char *, void *, size_t);
char *se, *s0, *s, *q, qchar, special[8];
int d, w, n, sign, prec, flags, width, lasterr;
x = 0;
lasterr = errno;
out = fn ? fn : (void *)__fmt_noop;
while (*format) {
if (*format != '%') {
for (n = 1; format[n]; ++n) {
if (format[n] == '%')
break;
}
if (out(format, arg, n) == -1)
return -1;
format += n;
continue;
}
if (!IsTiny()) {
if (format[1] == 's') { // FAST PATH: PLAIN STRING
s = va_arg(va, char *);
if (!s)
s = "(null)";
if (out(s, arg, strlen(s)) == -1)
return -1;
format += 2;
continue;
} else if (format[1] == 'd') { // FAST PATH: PLAIN INTEGER
d = va_arg(va, int);
if (out(ibuf, arg, FormatInt32(ibuf, d) - ibuf) == -1)
return -1;
format += 2;
continue;
} else if (format[1] == 'u') { // FAST PATH: PLAIN UNSIGNED
u = va_arg(va, unsigned);
if (out(ibuf, arg, FormatUint32(ibuf, u) - ibuf) == -1)
return -1;
format += 2;
continue;
} else if (format[1] == 'x') { // FAST PATH: PLAIN HEX
u = va_arg(va, unsigned);
if (out(ibuf, arg, uint64toarray_radix16(u, ibuf)) == -1)
return -1;
format += 2;
continue;
} else if (format[1] == 'l' && format[2] == 'x') {
lu = va_arg(va, unsigned long); // FAST PATH: PLAIN LONG HEX
if (out(ibuf, arg, uint64toarray_radix16(lu, ibuf)) == -1)
return -1;
format += 3;
continue;
} else if (format[1] == 'l' && format[2] == 'd') {
ld = va_arg(va, long); // FAST PATH: PLAIN LONG
if (out(ibuf, arg, FormatInt64(ibuf, ld) - ibuf) == -1)
return -1;
format += 3;
continue;
} else if (format[1] == 'l' && format[2] == 'u') {
lu = va_arg(va, unsigned long); // FAST PATH: PLAIN UNSIGNED LONG
if (out(ibuf, arg, FormatUint64(ibuf, lu) - ibuf) == -1)
return -1;
format += 3;
continue;
} else if (format[1] == '.' && format[2] == '*' && format[3] == 's') {
n = va_arg(va, unsigned); // FAST PATH: PRECISION STRING
const char *s = va_arg(va, const char *);
if (s) {
n = strnlen(s, n);
} else {
s = "(null)";
n = MIN(6, n);
}
if (out(s, arg, n) == -1)
return -1;
format += 4;
continue;
}
}
// GENERAL PATH
format++;
sign = 0;
flags = 0;
getflag:
switch (*format++) {
case '0':
flags |= FLAGS_ZEROPAD;
goto getflag;
case '-':
flags |= FLAGS_LEFT;
goto getflag;
case '+':
sign = '+';
flags |= FLAGS_PLUS;
goto getflag;
case ' ':
sign = ' ';
flags |= FLAGS_SPACE;
goto getflag;
case '#':
flags |= FLAGS_HASH;
goto getflag;
case ',':
flags |= FLAGS_GROUPING;
goto getflag;
case '`':
flags |= FLAGS_REPR;
// fallthrough
case '\'':
flags |= FLAGS_QUOTE;
goto getflag;
default:
format--;
break;
}
// evaluate width field
width = 0;
if (isdigit(*format)) {
width = __fmt_atoi(&format);
} else if (*format == '*') {
w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = -w;
sign = '-';
} else {
width = w;
}
format++;
}
// evaluate prec field
prec = 0;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (isdigit(*format)) {
prec = __fmt_atoi(&format);
} else if (*format == '*') {
prec = va_arg(va, int);
format++;
}
}
if (prec < 0) {
prec = 0;
flags &= ~FLAGS_PRECISION;
}
// evaluate length field
signbit = 31;
longdouble = false;
switch (*format) {
case 'j': // intmax_t
format++;
signbit = sizeof(intmax_t) * 8 - 1;
if (*format == 'j') {
format++;
signbit = sizeof(int128_t) * 8 - 1;
}
break;
case 'l':
if (format[1] == 'f' || format[1] == 'F') {
format++;
break;
}
if (format[1] == 'l')
format++;
// fallthrough
case 't': // ptrdiff_t
case 'z': // size_t
case 'Z': // size_t
format++;
signbit = 63;
break;
case 'L': // long double
format++;
#if !(LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024)
longdouble = true;
#endif
break;
case 'h':
format++;
if (*format == 'h') {
format++;
signbit = 7;
} else {
signbit = 15;
}
break;
default:
break;
}
// evaluate specifier
qchar = '"';
log2base = 0;
alphabet = "0123456789abcdefpx";
switch ((d = *format++)) {
case 'p':
flags |= FLAGS_HASH;
log2base = 4;
signbit = 63;
goto FormatNumber;
case 'X':
alphabet = "0123456789ABCDEFPX";
// fallthrough
case 'x':
log2base = 4;
goto FormatNumber;
case 'b':
log2base = 1;
alphabet = "0123456789abcdefpb";
goto FormatNumber;
case 'o':
log2base = 3;
goto FormatNumber;
case 'd':
case 'i':
flags |= FLAGS_ISSIGNED;
// fallthrough
case 'u': {
uint128_t value;
flags &= ~FLAGS_HASH; // no hash for dec format
FormatNumber:
if (signbit > 63) {
value = va_arg(va, uint128_t);
} else {
value = va_arg(va, uint64_t);
}
if (__fmt_ntoa(out, arg, value, signbit, log2base, prec, width, flags,
alphabet) == -1) {
return -1;
}
break;
}
case 'c':
if ((charbuf[0] = va_arg(va, int))) {
p = charbuf;
qchar = '\'';
flags |= FLAGS_PRECISION;
prec = 1;
goto FormatString;
} else if (flags & (FLAGS_QUOTE | FLAGS_REPR)) {
p = "'\\0'";
flags &= ~(FLAGS_QUOTE | FLAGS_REPR | FLAGS_HASH);
goto FormatString;
} else if (flags & FLAGS_HASH) {
flags &= ~FLAGS_HASH;
p = " ";
goto FormatString;
} else {
__FMT_PUT('\0');
break;
}
case 'm':
p = strerror(lasterr);
signbit = 0;
goto FormatString;
case 'r':
// undocumented %r specifier
// used for good carriage return
// helps integrate loggers with repls
if (!__ttyconf.replstderr) {
break;
} else {
p = "\r\e[K";
goto FormatString;
}
case 'S':
// Specified by POSIX to be equivalent to %ls
signbit = 63;
goto FormatStringPNotFetchedYet;
case 'q':
flags |= FLAGS_QUOTE;
// fallthrough
case 's':
FormatStringPNotFetchedYet:
p = va_arg(va, void *);
FormatString:
if (__fmt_stoa(out, arg, p, flags, prec, width, signbit, qchar) == -1) {
return -1;
}
break;
case 'n':
__FMT_PUT('\n');
break;
case 'F':
case 'f':
if (!(flags & FLAGS_PRECISION))
prec = 6;
if (!longdouble) {
x = va_arg(va, double);
s = s0 = dtoa(x, 3, prec, &decpt, &fpb.sign, &se);
if (decpt == 9999) {
if (s && s[0] == 'N') {
fpb.kind = STRTOG_NaN;
} else {
fpb.kind = STRTOG_Infinite;
}
}
} else {
un.ld = va_arg(va, long double);
__fmt_ldfpbits(&un, &fpb);
s = s0 =
gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, 3, prec, &decpt, &se);
}
if (decpt == 9999) {
Format9999:
if (s0)
freedtoa(s0);
bzero(special, sizeof(special));
s = q = special;
if (fpb.sign) {
*q++ = '-';
} else if (flags & FLAGS_PLUS) {
*q++ = '+';
} else if (flags & FLAGS_SPACE) {
*q++ = ' ';
}
memcpy(q, kSpecialFloats[fpb.kind == STRTOG_NaN][d >= 'a'], 4);
flags &= ~(FLAGS_PRECISION | FLAGS_PLUS | FLAGS_HASH | FLAGS_SPACE);
prec = 0;
rc = __fmt_stoa(out, arg, s, flags, prec, width, signbit, qchar);
if (rc == -1)
return -1;
break;
}
FormatReal:
if (fpb.sign /* && (x || sign) */)
sign = '-';
if (prec > 0)
width -= prec;
if (width > 0) {
if (sign)
--width;
if (decpt <= 0) {
--width;
if (prec > 0)
--width;
} else {
if (s == se)
decpt = 1;
width -= decpt;
if (prec > 0 || (flags & FLAGS_HASH))
--width;
}
}
if (width > 0 && !(flags & FLAGS_LEFT)) {
if ((flags & FLAGS_ZEROPAD)) {
if (sign)
__FMT_PUT(sign);
sign = 0;
do
__FMT_PUT('0');
while (--width > 0);
} else
do
__FMT_PUT(' ');
while (--width > 0);
}
if (sign)
__FMT_PUT(sign);
if (decpt <= 0) {
__FMT_PUT('0');
if (prec > 0 || (flags & FLAGS_HASH))
__FMT_PUT('.');
while (decpt < 0) {
__FMT_PUT('0');
prec--;
decpt++;
}
} else {
do {
if ((c = *s)) {
s++;
} else {
c = '0';
}
__FMT_PUT(c);
} while (--decpt > 0);
if (prec > 0 || (flags & FLAGS_HASH))
__FMT_PUT('.');
}
while (--prec >= 0) {
if ((c = *s)) {
s++;
} else {
c = '0';
}
__FMT_PUT(c);
}
while (--width >= 0)
__FMT_PUT(' ');
if (s0)
freedtoa(s0);
break;
case 'G':
case 'g':
if (!(flags & FLAGS_PRECISION))
prec = 6;
if (prec < 1)
prec = 1;
if (!longdouble) {
x = va_arg(va, double);
s = s0 = dtoa(x, 2, prec, &decpt, &fpb.sign, &se);
if (decpt == 9999) {
if (s && s[0] == 'N') {
fpb.kind = STRTOG_NaN;
} else {
fpb.kind = STRTOG_Infinite;
}
}
} else {
un.ld = va_arg(va, long double);
__fmt_ldfpbits(&un, &fpb);
s = s0 = gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
prec, &decpt, &se);
}
if (decpt == 9999)
goto Format9999;
c = se - s;
prec1 = prec;
if (!prec) {
prec = c;
prec1 = c + (s[1] || (flags & FLAGS_HASH) ? 5 : 4);
// %.0g gives 10 rather than 1e1
}
if (decpt > -4 && decpt <= prec1) {
if ((flags & FLAGS_HASH))
prec -= decpt;
else
prec = c - decpt;
if (prec < 0)
prec = 0;
goto FormatReal;
}
d -= 2;
if (!(flags & FLAGS_HASH) && prec > c)
prec = c;
--prec;
goto FormatExpo;
case 'e':
case 'E':
if (!(flags & FLAGS_PRECISION))
prec = 6;
if (prec < 0)
prec = 0;
if (!longdouble) {
x = va_arg(va, double);
s = s0 = dtoa(x, 2, prec + 1, &decpt, &fpb.sign, &se);
if (decpt == 9999) {
if (s && s[0] == 'N') {
fpb.kind = STRTOG_NaN;
} else {
fpb.kind = STRTOG_Infinite;
}
}
} else {
un.ld = va_arg(va, long double);
__fmt_ldfpbits(&un, &fpb);
s = s0 = gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
prec, &decpt, &se);
}
if (decpt == 9999)
goto Format9999;
FormatExpo:
if (fpb.sign /* && (x || sign) */)
sign = '-';
if ((width -= prec + 5) > 0) {
if (sign)
--width;
if (prec || (flags & FLAGS_HASH))
--width;
}
if ((c = --decpt) < 0)
c = -c;
while (c >= 100) {
--width;
c /= 10;
}
if (width > 0 && !(flags & FLAGS_LEFT)) {
if ((flags & FLAGS_ZEROPAD)) {
if (sign)
__FMT_PUT(sign);
sign = 0;
do
__FMT_PUT('0');
while (--width > 0);
} else
do
__FMT_PUT(' ');
while (--width > 0);
}
if (sign)
__FMT_PUT(sign);
__FMT_PUT(*s++);
if (prec || (flags & FLAGS_HASH))
__FMT_PUT('.');
while (--prec >= 0) {
if ((c = *s)) {
s++;
} else {
c = '0';
}
__FMT_PUT(c);
}
__FMT_PUT(d);
if (decpt < 0) {
__FMT_PUT('-');
decpt = -decpt;
} else {
__FMT_PUT('+');
}
for (c = 2, k = 10; 10 * k <= decpt; c++, k *= 10) {
donothing;
}
for (;;) {
i1 = decpt / k;
__FMT_PUT(i1 + '0');
if (--c <= 0)
break;
decpt -= i1 * k;
decpt *= 10;
}
while (--width >= 0) {
__FMT_PUT(' ');
}
freedtoa(s0);
break;
case 'A':
alphabet = "0123456789ABCDEFPX";
goto FormatBinary;
case 'a':
alphabet = "0123456789abcdefpx";
FormatBinary:
if (longdouble) {
un.ld = va_arg(va, long double);
__fmt_ldfpbits(&un, &fpb);
} else {
un.d = va_arg(va, double);
__fmt_dfpbits(&un, &fpb);
}
if (fpb.kind == STRTOG_Infinite || fpb.kind == STRTOG_NaN) {
s0 = 0;
goto Format9999;
}
prec1 = __fmt_fpiprec(&fpb);
if ((flags & FLAGS_PRECISION) && prec < prec1) {
prec1 = __fmt_bround(&fpb, prec, prec1);
}
bw = 1;
bex = fpb.ex + 4 * prec1;
if (bex) {
if ((i1 = bex) < 0)
i1 = -i1;
while (i1 >= 10) {
++bw;
i1 /= 10;
}
}
if (fpb.sign /* && (sign || fpb.kind != STRTOG_Zero) */) {
sign = '-';
}
if ((width -= bw + 5) > 0) {
if (sign)
--width;
if (prec1 || (flags & FLAGS_HASH))
--width;
}
if ((width -= MAX(prec, prec1)) > 0 && !(flags & FLAGS_LEFT) &&
!(flags & FLAGS_ZEROPAD)) {
do
__FMT_PUT(' ');
while (--width > 0);
}
if (sign)
__FMT_PUT(sign);
__FMT_PUT('0');
__FMT_PUT(alphabet[17]); // x or X
if ((flags & FLAGS_ZEROPAD) && width > 0 && !(flags & FLAGS_LEFT)) {
do
__FMT_PUT('0');
while (--width > 0);
}
i1 = prec1 & 7;
k = prec1 >> 3;
__FMT_PUT(alphabet[(fpb.bits[k] >> 4 * i1) & 0xf]);
if (prec1 > 0 || prec > 0) {
__FMT_PUT('.');
}
while (prec1 > 0) {
if (--i1 < 0) {
if (--k < 0)
break;
i1 = 7;
}
__FMT_PUT(alphabet[(fpb.bits[k] >> 4 * i1) & 0xf]);
--prec1;
--prec;
}
while (prec-- > 0) {
__FMT_PUT('0');
}
__FMT_PUT(alphabet[16]); // p or P
if (bex < 0) {
__FMT_PUT('-');
bex = -bex;
} else {
__FMT_PUT('+');
}
for (c = 1; 10 * c <= bex; c *= 10) {
donothing;
}
for (;;) {
i1 = bex / c;
__FMT_PUT('0' + i1);
if (!--bw)
break;
bex -= i1 * c;
bex *= 10;
}
while (--width >= 0) {
__FMT_PUT(' ');
}
break;
case '%':
__FMT_PUT('%');
break;
default:
__FMT_PUT(format[-1]);
break;
}
}
return 0;
}