From f064183646d86cba92df7a1d155f5f48cb5797b0 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 5 Mar 2021 10:31:16 -0800 Subject: [PATCH] Support proper %g, %f, and %a float formatting See #61 See #104 --- examples/hello4.c | 21 - libc/calls/vdprintf.c | 2 +- libc/fmt/conv.h | 1 - libc/fmt/fmt.c | 657 ++++++++++++++++++ libc/fmt/fmt.h | 2 +- libc/fmt/fmts.h | 20 + libc/fmt/internal.h | 16 + libc/fmt/itoa.c | 13 +- libc/fmt/ntoa.c | 174 +++++ libc/fmt/{spacepad.c => pad.c} | 4 +- libc/fmt/paland.inc | 48 -- libc/fmt/palandftoa.c | 170 ----- libc/fmt/palandntoa.c | 178 ----- libc/fmt/palandprintf.c | 337 --------- libc/fmt/palandprintf.h | 20 - libc/fmt/pflink.h | 30 +- libc/fmt/snprintf.c | 2 +- libc/fmt/sprintf.c | 2 +- libc/fmt/stoa.c | 69 +- libc/fmt/strerror_r.c | 3 - libc/fmt/strtol.c | 6 +- libc/fmt/vsnprintf.c | 4 +- libc/fmt/vsprintf.c | 2 +- libc/fmt/wcslol.internal.h | 16 - libc/fmt/wcstol.c | 4 +- libc/log/checkaligned.c | 3 - libc/log/checkfail.c | 4 - libc/log/log.mk | 3 +- libc/log/malloc_stats.c | 2 - libc/log/meminfo.c | 3 - libc/log/memsummary.c | 2 - libc/log/vflogf.c | 4 - libc/runtime/printmemoryintervals.c | 3 - .../rounddecimalplaces.c => stdio/dtoa.c} | 15 +- libc/stdio/printf.c | 2 +- libc/stdio/stdio.h | 3 + libc/stdio/stdio.mk | 3 +- libc/stdio/vfprintf.c | 2 +- libc/testlib/ezbenchreport.c | 2 - libc/time/asctime_r.c | 3 - libc/time/strftime.c | 6 +- libc/time/xiso8601.c | 3 - test/libc/fmt/palandprintf_test.c | 3 +- test/libc/fmt/realfmt_test.c | 68 ++ test/libc/fmt/test.mk | 1 + third_party/duktape/duktape.mk | 7 +- third_party/gdtoa/dtoa.c | 4 +- third_party/getopt/getopt.c | 8 +- 48 files changed, 1034 insertions(+), 921 deletions(-) delete mode 100644 examples/hello4.c create mode 100644 libc/fmt/fmt.c create mode 100644 libc/fmt/fmts.h create mode 100644 libc/fmt/internal.h create mode 100644 libc/fmt/ntoa.c rename libc/fmt/{spacepad.c => pad.c} (94%) delete mode 100644 libc/fmt/paland.inc delete mode 100644 libc/fmt/palandftoa.c delete mode 100644 libc/fmt/palandntoa.c delete mode 100644 libc/fmt/palandprintf.c delete mode 100644 libc/fmt/palandprintf.h delete mode 100644 libc/fmt/wcslol.internal.h rename libc/{fmt/rounddecimalplaces.c => stdio/dtoa.c} (85%) create mode 100644 test/libc/fmt/realfmt_test.c diff --git a/examples/hello4.c b/examples/hello4.c deleted file mode 100644 index 7a104c40..00000000 --- a/examples/hello4.c +++ /dev/null @@ -1,21 +0,0 @@ -#if 0 -/*─────────────────────────────────────────────────────────────────╗ -│ To the extent possible under law, Justine Tunney has waived │ -│ all copyright and related or neighboring rights to this file, │ -│ as it is written in the following disclaimers: │ -│ • http://unlicense.org/ │ -│ • http://creativecommons.org/publicdomain/zero/1.0/ │ -╚─────────────────────────────────────────────────────────────────*/ -#endif -#include "libc/errno.h" -#include "libc/stdio/stdio.h" - -int main(int argc, char *argv[]) { - printf("abcdefghijklmnopqrstuvwxyz " - "ABCDEFGHIJKLMNOPQRSTUVWXYZ " - "!@#$$%%^&*(){}%%* " - "0123456789 " - "%3d\n", - argc); - return errno; -} diff --git a/libc/calls/vdprintf.c b/libc/calls/vdprintf.c index 394280d2..8f4f6370 100644 --- a/libc/calls/vdprintf.c +++ b/libc/calls/vdprintf.c @@ -58,7 +58,7 @@ int(vdprintf)(int fd, const char *fmt, va_list va) { struct VdprintfState df; df.n = 0; df.fd = fd; - if (palandprintf(vdprintfputchar, &df, fmt, va) == -1) return -1; + if (__fmt(vdprintfputchar, &df, fmt, va) == -1) return -1; if (vdprintf_flush(&df, df.n & (ARRAYLEN(df.buf) - 1)) == -1) return -1; return df.n; } diff --git a/libc/fmt/conv.h b/libc/fmt/conv.h index ff31aaae..ae924e67 100644 --- a/libc/fmt/conv.h +++ b/libc/fmt/conv.h @@ -82,7 +82,6 @@ div_t div(int, int) pureconst; ldiv_t ldiv(long, long) pureconst; lldiv_t lldiv(long long, long long) pureconst; imaxdiv_t imaxdiv(intmax_t, intmax_t) pureconst; -double RoundDecimalPlaces(double, double, double (*)(double)); /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § conversion » optimizations ─╬─│┼ diff --git a/libc/fmt/fmt.c b/libc/fmt/fmt.c new file mode 100644 index 00000000..4d21be24 --- /dev/null +++ b/libc/fmt/fmt.c @@ -0,0 +1,657 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" +#include "libc/fmt/conv.h" +#include "libc/fmt/fmt.h" +#include "libc/fmt/fmts.h" +#include "libc/fmt/internal.h" +#include "libc/mem/mem.h" +#include "libc/runtime/internal.h" +#include "libc/str/str.h" +#include "libc/sysv/errfuns.h" +#include "third_party/gdtoa/gdtoa.h" + +static int __fmt_atoi(const char **str) { + int i; + for (i = 0; '0' <= **str && **str <= '9'; ++*str) { + i *= 10; + i += **str - '0'; + } + return i; +} + +/** + * 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) + * - `%Lg` long double + * + * 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 (128-bit) + * + * 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) + * + * @note implementation detail of printf(), snprintf(), etc. + * @see printf() for wordier documentation + * @note netlib.org is so helpful + * @asyncsignalsafe + * @vforksafe + */ +hidden int __fmt(void *fn, void *arg, const char *format, va_list va) { + union { + double d; + unsigned int u[2]; + } pun; + void *p; + char qchar; + char *s, *se; + bool longdouble; + long double ldbl; + wchar_t charbuf[1]; + const char *alphabet; + int (*out)(long, void *); + unsigned char signbit, log2base; + int c, d, k, w, i1, ui, bw, bex; + int sgn, alt, sign, prec, prec1, flags, width, decpt, lasterr; + + lasterr = errno; + out = fn ? fn : (void *)missingno; + + while (*format) { + /* %[flags][width][.prec][length] */ + if (*format != '%') { + /* no */ + if (out(*format, arg) == -1) return -1; + format++; + continue; + } else { + /* yes, evaluate it */ + format++; + } + + /* evaluate flags */ + 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; + } + + /* evaluate length field */ + signbit = 31; + longdouble = false; + switch (*format) { + case 'j': /* intmax_t */ + format++; + signbit = sizeof(intmax_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++; + longdouble = true; + break; + case 'h': + format++; + if (*format == 'h') { + format++; + signbit = 7; + } else { + signbit = 15; + } + break; + default: + break; + } + + /* evaluate specifier */ + qchar = '"'; + log2base = 0; + alphabet = "0123456789abcdef"; + switch ((d = *format++)) { + case 'p': + flags |= FLAGS_ZEROPAD; + width = POINTER_XDIGITS; + log2base = 4; + signbit = 47; + goto FormatNumber; + case 'X': + alphabet = "0123456789ABCDEF"; + /* fallthrough */ + case 'x': + log2base = 4; + goto FormatNumber; + case 'b': + log2base = 1; + goto FormatNumber; + case 'o': + log2base = 3; + goto FormatNumber; + case 'd': + case 'i': + flags |= FLAGS_ISSIGNED; + /* fallthrough */ + case 'u': { + flags &= ~FLAGS_HASH; /* no hash for dec format */ + FormatNumber: + if (__fmt_ntoa(out, arg, va, signbit, log2base, prec, width, flags, + alphabet) == -1) { + return -1; + } + break; + } + + case 'c': + prec = 1; + flags |= FLAGS_PRECISION; + qchar = '\''; + p = charbuf; + charbuf[0] = va_arg(va, int); + goto FormatString; + + case 'm': + p = weaken(strerror) ? weaken(strerror)(lasterr) : "?"; + signbit = 0; + goto FormatString; + + case 'r': + flags |= FLAGS_REPR; + /* fallthrough */ + + case 'q': + flags |= FLAGS_QUOTE; + /* fallthrough */ + + case 's': + p = va_arg(va, void *); + FormatString: + if (__fmt_stoa(out, arg, p, flags, prec, width, signbit, qchar) == -1) { + return -1; + } + break; + + case 'f': + if (!(flags & FLAGS_PRECISION)) prec = 6; + if (longdouble) { + pun.d = va_arg(va, long double); + } else { + pun.d = va_arg(va, double); + } + FormatDtoa: + if (!weaken(__fmt_dtoa)) { + p = "nan"; + goto FormatString; + } + s = weaken(__fmt_dtoa)(pun.d, 3, prec, &decpt, &sgn, &se); + if (decpt == 9999) { + Format9999: + prec = alt = 0; + flags &= ~FLAGS_PRECISION; + if (*s == 'N') { + p = s; + goto FormatString; + } + decpt = strlen(s); + } + FormatReal: + if (sgn) 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 || alt) --width; + } + } + if (width > 0 && !(flags & FLAGS_LEFT)) { + if (flags & FLAGS_ZEROPAD) { + if (sign) out(sign, arg); + sign = 0; + do out('0', arg); + while (--width > 0); + } else + do out(' ', arg); + while (--width > 0); + } + if (sign) out(sign, arg); + if (decpt <= 0) { + out('0', arg); + if (prec > 0 || alt) out('.', arg); + while (decpt < 0) { + out('0', arg); + prec--; + decpt++; + } + } else { + do { + if ((c = *s)) { + s++; + } else { + c = '0'; + } + out(c, arg); + } while (--decpt > 0); + if (prec > 0 || alt) out('.', arg); + } + while (--prec >= 0) { + if ((c = *s)) { + s++; + } else { + c = '0'; + } + out(c, arg); + } + while (--width >= 0) { + out(' ', arg); + } + continue; + + case 'G': + case 'g': + if (!(flags & FLAGS_PRECISION)) prec = 6; + if (longdouble) { + pun.d = va_arg(va, long double); + } else { + pun.d = va_arg(va, double); + } + if (prec < 0) prec = 0; + if (!weaken(__fmt_dtoa)) { + p = "nan"; + goto FormatString; + } + s = weaken(__fmt_dtoa)(pun.d, prec ? 2 : 0, prec, &decpt, &sgn, &se); + if (decpt == 9999) goto Format9999; + c = se - s; + prec1 = prec; + if (!prec) { + prec = c; + prec1 = c + (s[1] || alt ? 5 : 4); + /* %.0g gives 10 rather than 1e1 */ + } + if (decpt > -4 && decpt <= prec1) { + if (alt) { + prec -= decpt; + } else { + prec = c - decpt; + } + if (prec < 0) prec = 0; + goto FormatReal; + } + d -= 2; + if (!alt && prec > c) prec = c; + --prec; + goto FormatExpo; + + case 'e': + case 'E': + if (!(flags & FLAGS_PRECISION)) prec = 6; + if (longdouble) { + pun.d = va_arg(va, long double); + } else { + pun.d = va_arg(va, double); + } + if (prec < 0) prec = 0; + if (!weaken(__fmt_dtoa)) { + p = "nan"; + goto FormatString; + } + s = weaken(__fmt_dtoa)(pun.d, 2, prec + 1, &decpt, &sgn, &se); + if (decpt == 9999) goto Format9999; + FormatExpo: + if (sgn) sign = '-'; + if ((width -= prec + 5) > 0) { + if (sign) --width; + if (prec || alt) --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) out(sign, arg); + sign = 0; + do out('0', arg); + while (--width > 0); + } else { + do out(' ', arg); + while (--width > 0); + } + } + if (sign) out(sign, arg); + out(*s++, arg); + if (prec || alt) out('.', arg); + while (--prec >= 0) { + if ((c = *s)) { + s++; + } else { + c = '0'; + } + out(c, arg); + } + out(d, arg); + if (decpt < 0) { + out('-', arg); + decpt = -decpt; + } else { + out('+', arg); + } + for (c = 2, k = 10; 10 * k <= decpt; c++, k *= 10) { + } + for (;;) { + i1 = decpt / k; + out(i1 + '0', arg); + if (--c <= 0) break; + decpt -= i1 * k; + decpt *= 10; + } + while (--width >= 0) { + out(' ', arg); + } + continue; + + case 'a': + alphabet = "0123456789abcdefpx"; + goto FormatBinary; + case 'A': + alphabet = "0123456789ABCDEFPX"; + FormatBinary: + if (longdouble) { + pun.d = va_arg(va, long double); + } else { + pun.d = va_arg(va, double); + } + if ((pun.u[1] & 0x7ff00000) == 0x7ff00000) { + goto FormatDtoa; + } + if (pun.d) { + c = '1'; + if (pun.u[1] & 0x80000000) { + sign = '-'; + pun.u[1] &= 0x7fffffff; + } + bex = (pun.u[1] >> 20) - 1023; + pun.u[1] &= 0xfffff; + if (bex == -1023) { + ++bex; + if (pun.u[1]) { + do { + --bex; + pun.u[1] <<= 1; + if (pun.u[0] & 0x80000000) pun.u[1] |= 1; + pun.u[0] <<= 1; + } while (pun.u[1] < 0x100000); + } else { + while (!(pun.u[0] & 0x80000000)) { + --bex; + pun.u[0] <<= 1; + } + bex -= 21; + pun.u[1] = pun.u[0] >> 11; + pun.u[0] <<= 21; + } + } + } else { + c = '0'; + bex = 0; + } + if (flags & FLAGS_PRECISION) { + if (prec > 13) prec = 13; + if (pun.d && prec < 13) { + pun.u[1] |= 0x100000; + if (prec < 5) { + ui = 1 << ((5 - prec) * 4 - 1); + if (pun.u[1] & ui) { + if (pun.u[1] & ((ui - 1) | (ui << 1)) || pun.u[0]) { + pun.u[1] += ui; + BexCheck: + if (pun.u[1] & 0x200000) { + ++bex; + pun.u[1] >>= 1; + } + } + } + } else if (prec == 5) { + if (pun.u[0] & 0x80000000) { + BumpIt: + ++pun.u[1]; + goto BexCheck; + } + } else { + i1 = (13 - prec) * 4; + ui = 1 << (i1 - 1); + if (pun.u[0] & ui && pun.u[0] & ((ui - 1) | (ui << 1))) { + pun.u[0] += ui; + if (!(pun.u[0] >> i1)) goto BumpIt; + } + } + } + } else { + if ((ui = pun.u[0])) { + for (prec = 6; (ui = (ui << 4) & 0xffffffff); ++prec) { + } + } else { + for (prec = 0, ui = pun.u[1] & 0xfffff; ui; + ++prec, ui = (ui << 4) & 0xfffff) { + } + } + } + bw = 1; + if (bex) { + if ((i1 = bex) < 0) i1 = -i1; + while (i1 >= 10) { + ++bw; + i1 /= 10; + } + } + if ((sgn = pun.u[1] & 0x80000000)) { + pun.u[1] &= 0x7fffffff; + if (pun.d || sign) sign = '-'; + } + if ((width -= bw + 5) > 0) { + if (sign) --width; + if (prec || alt) --width; + } + if (width > 0 && !(flags & FLAGS_LEFT)) { + if (flags & FLAGS_ZEROPAD) { + if (sign) { + out(sign, arg); + sign = 0; + } + do out('0', arg); + while (--width > 0); + } else { + do out(' ', arg); + while (--width > 0); + } + } + if (sign) out(sign, arg); + out('0', arg); + out(alphabet[17], arg); + out(c, arg); + if (prec > 0 || alt) out('.', arg); + if (prec > 0) { + if ((i1 = prec) > 5) i1 = 5; + prec -= i1; + do { + out(alphabet[(pun.u[1] >> 16) & 0xf], arg); + pun.u[1] <<= 4; + } while (--i1 > 0); + while (prec > 0) { + --prec; + out(alphabet[(pun.u[0] >> 28) & 0xf], arg); + pun.u[0] <<= 4; + } + } + out(alphabet[16], arg); + if (bex < 0) { + out('-', arg); + bex = -bex; + } else { + out('+', arg); + } + for (c = 1; 10 * c <= bex; c *= 10) { + } + for (;;) { + i1 = bex / c; + out('0' + i1, arg); + if (!--bw) break; + bex -= i1 * c; + bex *= 10; + } + continue; + + case '%': + if (out('%', arg) == -1) return -1; + break; + + default: + if (out(format[-1], arg) == -1) return -1; + break; + } + } + return 0; +} diff --git a/libc/fmt/fmt.h b/libc/fmt/fmt.h index 8cbbb62d..6ecc118a 100644 --- a/libc/fmt/fmt.h +++ b/libc/fmt/fmt.h @@ -27,7 +27,7 @@ int vsscanf(const char *, const char *, va_list); int vcscanf(int (*)(void *), int (*)(int, void *), void *, const char *, va_list); int strerror_r(int, char *, size_t) nothrow nocallback; -int palandprintf(void *, void *, const char *, va_list) hidden; +int __fmt(void *, void *, const char *, va_list) hidden; char *itoa(int, char *, int) compatfn; char *fcvt(double, int, int *, int *); char *ecvt(double, int, int *, int *); diff --git a/libc/fmt/fmts.h b/libc/fmt/fmts.h new file mode 100644 index 00000000..4336f7bc --- /dev/null +++ b/libc/fmt/fmts.h @@ -0,0 +1,20 @@ +#ifndef COSMOPOLITAN_LIBC_FMT_FMTS_H_ +#define COSMOPOLITAN_LIBC_FMT_FMTS_H_ + +#define PRINTF_NTOA_BUFFER_SIZE 144 + +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +int __fmt_pad(int (*)(long, void *), void *, unsigned long) hidden; +int __fmt_stoa(int (*)(long, void *), void *, void *, unsigned long, + unsigned long, unsigned long, unsigned char, + unsigned char) hidden; +int __fmt_ntoa(int (*)(long, void *), void *, va_list, unsigned char, + unsigned long, unsigned long, unsigned long, unsigned char, + const char *) hidden; +char *__fmt_dtoa(double, int, int, int *, int *, char **) hidden; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_FMT_FMTS_H_ */ diff --git a/libc/fmt/internal.h b/libc/fmt/internal.h new file mode 100644 index 00000000..2ce4642b --- /dev/null +++ b/libc/fmt/internal.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_FMT_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_FMT_INTERNAL_H_ + +#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 + +#endif /* COSMOPOLITAN_LIBC_FMT_INTERNAL_H_ */ diff --git a/libc/fmt/itoa.c b/libc/fmt/itoa.c index f42660a7..d513e374 100644 --- a/libc/fmt/itoa.c +++ b/libc/fmt/itoa.c @@ -19,13 +19,12 @@ #include "libc/fmt/conv.h" #include "libc/fmt/fmt.h" -STATIC_YOINK("ntoa"); - compatfn char *itoa(int value, char *str, int radix) { - (sprintf)( - str, - VEIL("r", - radix == 16 ? "%x" : radix == 8 ? "%d" : radix == 2 ? "%b" : "%d"), - value); + (sprintf)(str, + VEIL("r", radix == 16 ? "%x" + : radix == 8 ? "%d" + : radix == 2 ? "%b" + : "%d"), + value); return str; } diff --git a/libc/fmt/ntoa.c b/libc/fmt/ntoa.c new file mode 100644 index 00000000..25757d6c --- /dev/null +++ b/libc/fmt/ntoa.c @@ -0,0 +1,174 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/fmt/conv.h" +#include "libc/fmt/fmts.h" +#include "libc/fmt/internal.h" + +#define BUFFER_SIZE 144 + +uintmax_t __udivmodti4(uintmax_t, uintmax_t, uintmax_t *); + +static int __fmt_ntoa_format(int out(long, void *), void *arg, char *buf, + unsigned len, bool negative, unsigned log2base, + unsigned prec, unsigned width, + unsigned char flags) { + unsigned i, idx; + idx = 0; + + /* pad leading zeros */ + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && + (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + /* handle hash */ + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && + ((len == prec) || (len == width)) && buf[len - 1] == '0') { + len--; + if (len && (log2base == 4 || log2base == 1) && buf[len - 1] == '0') { + len--; + } + } + if (log2base == 4 && len < BUFFER_SIZE) { + buf[len++] = 'x'; + } else if (log2base == 1 && len < BUFFER_SIZE) { + buf[len++] = 'b'; + } + if (len < BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; /* ignore the space if the '+' exists */ + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + /* 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; + } + } + + /* reverse string */ + for (i = 0U; i < len; i++) { + if (out(buf[len - i - 1], arg) == -1) return -1; + idx++; + } + + /* append pad spaces up to given width */ + if (flags & FLAGS_LEFT) { + if (idx < width) { + if (__fmt_pad(out, arg, width - idx) == -1) return -1; + } + } + return 0; +} + +int __fmt_ntoa2(int out(long, void *), void *arg, uintmax_t value, bool neg, + unsigned log2base, unsigned prec, unsigned width, + unsigned flags, const char *alphabet) { + uintmax_t remainder; + unsigned len, count, digit; + char buf[BUFFER_SIZE]; + len = 0; + if (!value) flags &= ~FLAGS_HASH; + if (value || !(flags & FLAGS_PRECISION)) { + count = 0; + do { + assert(len < BUFFER_SIZE); + if (!log2base) { + 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); + } + return __fmt_ntoa_format(out, arg, buf, len, neg, log2base, prec, width, + flags); +} + +int __fmt_ntoa(int out(long, void *), void *arg, va_list va, + unsigned char signbit, unsigned long log2base, + unsigned long prec, unsigned long width, unsigned char flags, + const char *lang) { + bool neg; + uintmax_t value, sign; + + /* ignore '0' flag when prec is given */ + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + /* no plus / space flag for u, x, X, o, b */ + if (!(flags & FLAGS_ISSIGNED)) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + if (signbit > 63) { + value = va_arg(va, uint128_t); + } else { + value = va_arg(va, uint64_t); + } + + 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); +} diff --git a/libc/fmt/spacepad.c b/libc/fmt/pad.c similarity index 94% rename from libc/fmt/spacepad.c rename to libc/fmt/pad.c index 61c5dcb8..e6d50dcd 100644 --- a/libc/fmt/spacepad.c +++ b/libc/fmt/pad.c @@ -16,9 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/palandprintf.h" +#include "libc/fmt/fmts.h" -int spacepad(int out(long, void *), void *arg, unsigned long n) { +int __fmt_pad(int out(long, void *), void *arg, unsigned long n) { int i, rc; for (rc = i = 0; i < n; ++i) rc |= out(' ', arg); return rc; diff --git a/libc/fmt/paland.inc b/libc/fmt/paland.inc deleted file mode 100644 index 0f78f28b..00000000 --- a/libc/fmt/paland.inc +++ /dev/null @@ -1,48 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│ -╚══════════════════════════════════════════════════════════════════════════════╝ -│ @author (c) Marco Paland (info@paland.com) │ -│ 2014-2019, PALANDesign Hannover, Germany │ -│ │ -│ @license The MIT License (MIT) │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining a copy │ -│ of this software and associated documentation files (the "Software"), to deal│ -│ in the Software without restriction, including without limitation the rights │ -│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │ -│ copies of the Software, and to permit persons to whom the Software is │ -│ furnished to do so, subject to the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be included in │ -│ all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │ -│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │ -│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │ -│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │ -│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│ -│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │ -│ THE SOFTWARE. │ -└─────────────────────────────────────────────────────────────────────────────*/ - -asm(".ident\t\"\\n\\n\ -Paland Printf (MIT License)\\n\ -Copyright 2014-2019 Marco Paland\\n\ -PALANDesign Hannover, Germany\\n\ -info@paland.com\""); - -#include "libc/mem/mem.h" -#include "libc/str/internal.h" -#include "libc/sysv/errfuns.h" - -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_PRECISION (1U << 5U) -#define FLAGS_ISSIGNED (1U << 6U) -#define FLAGS_NOQUOTE (1U << 7U) -#define FLAGS_QUOTE FLAGS_SPACE -#define FLAGS_GROUPING FLAGS_NOQUOTE -#define FLAGS_REPR FLAGS_PLUS diff --git a/libc/fmt/palandftoa.c b/libc/fmt/palandftoa.c deleted file mode 100644 index fa3bbff9..00000000 --- a/libc/fmt/palandftoa.c +++ /dev/null @@ -1,170 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│ -╚══════════════════════════════════════════════════════════════════════════════╝ -│ @author (c) Marco Paland (info@paland.com) │ -│ 2014-2019, PALANDesign Hannover, Germany │ -│ │ -│ @license The MIT License (MIT) │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining a copy │ -│ of this software and associated documentation files (the "Software"), to deal│ -│ in the Software without restriction, including without limitation the rights │ -│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │ -│ copies of the Software, and to permit persons to whom the Software is │ -│ furnished to do so, subject to the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be included in │ -│ all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │ -│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │ -│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │ -│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │ -│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│ -│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │ -│ THE SOFTWARE. │ -│ │ -│ @brief Tiny printf, sprintf and (v)snprintf implementation, optimized for │ -│ embedded systems with a very limited resources. These routines are │ -│ thread safe and reentrant! Use this instead of the bloated │ -│ standard/newlib printf cause these use malloc for printf (and may not │ -│ be thread safe). │ -└─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/paland.inc" -#include "libc/fmt/palandprintf.h" -#include "libc/math.h" - -/** - * Formats floating point number. - * - * @see xdtoa() for higher precision at the cost of bloat - * @see palandprintf() which is intended caller - */ -int ftoa(int out(long, void *), void *arg, long double value, int prec, - unsigned long width, unsigned long flags) { - long whole, frac; - long double tmp, diff; - unsigned i, len, count, idx; - char buf[PRINTF_FTOA_BUFFER_SIZE]; - - len = 0; - diff = 0; - - if (isnan(value)) { - buf[0] = 'n'; - buf[1] = 'a'; - buf[2] = 'n'; - buf[3] = '\0'; - len += 3; - } else if (isinf(value) || (value && ilogbl(fabsl(value)) > 63)) { - buf[0] = 'f'; - buf[1] = 'n'; - buf[2] = 'i'; - buf[3] = '\0'; - len += 3; - } else { - - /* set default precision to 6, if not set explicitly */ - if (!(flags & FLAGS_PRECISION)) { - prec = 6; - } - - while (len < PRINTF_FTOA_BUFFER_SIZE && prec > 14) { - buf[len++] = '0'; - prec--; - } - - whole = truncl(fabsl(value)); - tmp = (fabsl(value) - whole) * exp10l(prec); - frac = tmp; - diff = tmp - frac; - - if (diff > .5) { - ++frac; /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ - if (frac >= exp10l(prec)) { - frac = 0; - ++whole; - } - } else if (diff < .5) { - } else if (!frac || (frac & 1)) { - ++frac; /* if halfway, round up if odd OR if last digit is 0 */ - } - - if (!prec) { - diff = fabsl(value) - whole; - if ((!(diff < .5) || (diff > .5)) && (whole & 1)) { - /* exactly .5 and ODD, then round up */ - /* 1.5 -> 2, but 2.5 -> 2 */ - ++whole; - } - } else { - count = prec; - /* now do fractional part, as an unsigned number */ - while (len < PRINTF_FTOA_BUFFER_SIZE) { - --count; - buf[len++] = 48 + (frac % 10); - if (!(frac /= 10)) { - break; - } - } - /* add extra 0s */ - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0)) { - buf[len++] = '0'; - } - if (len < PRINTF_FTOA_BUFFER_SIZE) { - /* add decimal */ - buf[len++] = '.'; - } - } - - /* do whole part, number is reversed */ - while (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = (char)(48 + (whole % 10)); - if (!(whole /= 10)) { - break; - } - } - - /* pad leading zeros */ - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { - if (width && (signbit(value) || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - } - - if (len < PRINTF_FTOA_BUFFER_SIZE) { - if (signbit(value)) { - buf[len++] = '-'; - } else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; /* ignore the space if the '+' exists */ - } else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - /* pad spaces up to given width */ - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - if (len < width) { - if (spacepad(out, arg, width - len) == -1) return -1; - } - } - - /* reverse string */ - for (idx = i = 0; i < len; i++) { - if (out(buf[len - i - 1U], arg) == -1) return -1; - idx++; - } - - /* append pad spaces up to given width */ - if (flags & FLAGS_LEFT) { - if (len < width) { - if (spacepad(out, arg, width - len) == -1) return -1; - } - } - - return 0; -} diff --git a/libc/fmt/palandntoa.c b/libc/fmt/palandntoa.c deleted file mode 100644 index 3404d7f5..00000000 --- a/libc/fmt/palandntoa.c +++ /dev/null @@ -1,178 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│ -╚══════════════════════════════════════════════════════════════════════════════╝ -│ @author (c) Marco Paland (info@paland.com) │ -│ 2014-2019, PALANDesign Hannover, Germany │ -│ │ -│ @license The MIT License (MIT) │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining a copy │ -│ of this software and associated documentation files (the "Software"), to deal│ -│ in the Software without restriction, including without limitation the rights │ -│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │ -│ copies of the Software, and to permit persons to whom the Software is │ -│ furnished to do so, subject to the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be included in │ -│ all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │ -│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │ -│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │ -│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │ -│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│ -│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │ -│ THE SOFTWARE. │ -└─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/fmt/conv.h" -#include "libc/fmt/paland.inc" -#include "libc/fmt/palandprintf.h" - -uintmax_t __udivmodti4(uintmax_t, uintmax_t, uintmax_t *); - -static int ntoaformat(int out(long, void *), void *arg, char *buf, unsigned len, - bool negative, unsigned log2base, unsigned prec, - unsigned width, unsigned char flags) { - unsigned i, idx; - idx = 0; - - /* pad leading zeros */ - if (!(flags & FLAGS_LEFT)) { - if (width && (flags & FLAGS_ZEROPAD) && - (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && - (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - /* handle hash */ - if (flags & FLAGS_HASH) { - if (!(flags & FLAGS_PRECISION) && len && - ((len == prec) || (len == width)) && buf[len - 1] == '0') { - len--; - if (len && (log2base == 4 || log2base == 1) && buf[len - 1] == '0') { - len--; - } - } - if (log2base == 4 && len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = 'x'; - } else if (log2base == 1 && len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = 'b'; - } - if (len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_NTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; /* ignore the space if the '+' exists */ - } else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - /* pad spaces up to given width */ - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - if (len < width) { - if (spacepad(out, arg, width - len) == -1) return -1; - } - } - - /* reverse string */ - for (i = 0U; i < len; i++) { - if (out(buf[len - i - 1], arg) == -1) return -1; - idx++; - } - - /* append pad spaces up to given width */ - if (flags & FLAGS_LEFT) { - if (idx < width) { - if (spacepad(out, arg, width - idx) == -1) return -1; - } - } - return 0; -} - -int ntoa2(int out(long, void *), void *arg, uintmax_t value, bool neg, - unsigned log2base, unsigned prec, unsigned width, unsigned flags, - const char *alphabet) { - uintmax_t remainder; - unsigned len, count, digit; - char buf[PRINTF_NTOA_BUFFER_SIZE]; - len = 0; - if (!value) flags &= ~FLAGS_HASH; - if (value || !(flags & FLAGS_PRECISION)) { - count = 0; - do { - assert(len < PRINTF_NTOA_BUFFER_SIZE); - if (!log2base) { - 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); - } - return ntoaformat(out, arg, buf, len, neg, log2base, prec, width, flags); -} - -int ntoa(int out(long, void *), void *arg, va_list va, unsigned char signbit, - unsigned long log2base, unsigned long prec, unsigned long width, - unsigned char flags, const char *lang) { - bool neg; - uintmax_t value, sign; - - /* ignore '0' flag when prec is given */ - if (flags & FLAGS_PRECISION) { - flags &= ~FLAGS_ZEROPAD; - } - - /* no plus / space flag for u, x, X, o, b */ - if (!(flags & FLAGS_ISSIGNED)) { - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - } - - if (signbit > 63) { - value = va_arg(va, uint128_t); - } else { - value = va_arg(va, uint64_t); - } - - 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 ntoa2(out, arg, value, neg, log2base, prec, width, flags, lang); -} diff --git a/libc/fmt/palandprintf.c b/libc/fmt/palandprintf.c deleted file mode 100644 index 002b1f02..00000000 --- a/libc/fmt/palandprintf.c +++ /dev/null @@ -1,337 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│ -╚══════════════════════════════════════════════════════════════════════════════╝ -│ @author (c) Marco Paland (info@paland.com) │ -│ 2014-2019, PALANDesign Hannover, Germany │ -│ │ -│ @license The MIT License (MIT) │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining a copy │ -│ of this software and associated documentation files (the "Software"), to deal│ -│ in the Software without restriction, including without limitation the rights │ -│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │ -│ copies of the Software, and to permit persons to whom the Software is │ -│ furnished to do so, subject to the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be included in │ -│ all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │ -│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │ -│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │ -│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │ -│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│ -│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │ -│ THE SOFTWARE. │ -│ │ -│ @brief Tiny printf, sprintf and (v)snprintf implementation, optimized for │ -│ embedded systems with a very limited resources. These routines are │ -│ thread safe and reentrant! Use this instead of the bloated │ -│ standard/newlib printf cause these use malloc for printf (and may not │ -│ be thread safe). │ -│ │ -│ @brief Modified by Justine Tunney to support three different types of │ -│ UNICODE, 128-bit arithmetic, binary conversion, string escaping, │ -│ AVX2 character scanning, and possibly a tinier footprint too, so │ -│ long as extremely wild linker hacks aren't considered cheating. │ -└─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/bits/bits.h" -#include "libc/bits/weaken.h" -#include "libc/fmt/conv.h" -#include "libc/fmt/fmt.h" -#include "libc/fmt/paland.inc" -#include "libc/fmt/palandprintf.h" -#include "libc/mem/mem.h" -#include "libc/runtime/internal.h" -#include "libc/str/str.h" -#include "libc/sysv/errfuns.h" - -static int ppatoi(const char **str) { - int i; - for (i = 0; '0' <= **str && **str <= '9'; ++*str) { - i *= 10; - i += **str - '0'; - } - return i; -} - -/** - * 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) - * - `%u` unsigned - * - `%f` double - * - `%Lf` long double - * - `%p` pointer (48-bit hexadecimal) - * - * 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 (128-bit) - * - * 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) - * - * @note implementation detail of printf(), snprintf(), etc. - * @see printf() for wordier documentation - * @asyncsignalsafe - * @vforksafe - */ -hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { - void *p; - char qchar; - bool longdouble; - long double ldbl; - wchar_t charbuf[1]; - const char *alphabet; - int (*out)(long, void *); - unsigned char signbit, log2base; - int w, flags, width, lasterr, precision; - - lasterr = errno; - out = fn ? fn : (void *)missingno; - - while (*format) { - /* %[flags][width][.precision][length] */ - if (*format != '%') { - /* no */ - if (out(*format, arg) == -1) return -1; - format++; - continue; - } else { - /* yes, evaluate it */ - format++; - } - - /* evaluate flags */ - flags = 0; - getflag: - switch (*format++) { - case '0': - flags |= FLAGS_ZEROPAD; - goto getflag; - case '-': - flags |= FLAGS_LEFT; - goto getflag; - case '+': - flags |= FLAGS_PLUS; - goto getflag; - case ' ': - 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 = ppatoi(&format); - } else if (*format == '*') { - w = va_arg(va, int); - if (w < 0) { - flags |= FLAGS_LEFT; /* reverse padding */ - width = -w; - } else { - width = w; - } - format++; - } - - /* evaluate precision field */ - precision = 0; - if (*format == '.') { - flags |= FLAGS_PRECISION; - format++; - if (isdigit(*format)) { - precision = ppatoi(&format); - } else if (*format == '*') { - precision = va_arg(va, int); - format++; - } - } - if (precision < 0) { - precision = 0; - } - - /* evaluate length field */ - signbit = 31; - longdouble = false; - switch (*format) { - case 'j': /* intmax_t */ - format++; - signbit = sizeof(intmax_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++; - longdouble = true; - break; - case 'h': - format++; - if (*format == 'h') { - format++; - signbit = 7; - } else { - signbit = 15; - } - break; - default: - break; - } - - /* evaluate specifier */ - alphabet = "0123456789abcdef"; - log2base = 0; - qchar = '"'; - switch (*format++) { - case 'p': - flags |= FLAGS_ZEROPAD; - width = POINTER_XDIGITS; - log2base = 4; - signbit = 47; - goto DoNumber; - case 'X': - alphabet = "0123456789ABCDEF"; - /* fallthrough */ - case 'x': - log2base = 4; - goto DoNumber; - case 'b': - log2base = 1; - goto DoNumber; - case 'o': - log2base = 3; - goto DoNumber; - case 'd': - case 'i': - flags |= FLAGS_ISSIGNED; - /* fallthrough */ - case 'u': { - flags &= ~FLAGS_HASH; /* no hash for dec format */ - DoNumber: - if (ntoa(out, arg, va, signbit, log2base, precision, width, flags, - alphabet) == -1) { - return -1; - } - break; - } - - case 'f': - case 'F': - if (longdouble) { - ldbl = va_arg(va, long double); - } else { - ldbl = va_arg(va, double); - } - if (ftoa(out, arg, ldbl, precision, width, flags) == -1) { - return -1; - } - break; - - case 'c': - precision = 1; - flags |= FLAGS_PRECISION; - qchar = '\''; - p = charbuf; - charbuf[0] = va_arg(va, int); /* assume little endian */ - goto showstr; - - case 'm': - p = weaken(strerror) ? weaken(strerror)(lasterr) : "?"; - signbit = 0; - goto showstr; - - case 'r': - flags |= FLAGS_REPR; - /* fallthrough */ - - case 'q': - flags |= FLAGS_QUOTE; - /* fallthrough */ - - case 's': - p = va_arg(va, void *); - showstr: - if (stoa(out, arg, p, flags, precision, width, signbit, qchar) == -1) { - return -1; - } - break; - - case '%': - if (out('%', arg) == -1) return -1; - break; - - default: - if (out(format[-1], arg) == -1) return -1; - break; - } - } - return 0; -} diff --git a/libc/fmt/palandprintf.h b/libc/fmt/palandprintf.h deleted file mode 100644 index a549c5d4..00000000 --- a/libc/fmt/palandprintf.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_ -#define COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_ - -#define PRINTF_NTOA_BUFFER_SIZE 144 -#define PRINTF_FTOA_BUFFER_SIZE 64 - -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -int spacepad(int (*)(long, void *), void *, unsigned long) hidden; -int ftoa(int (*)(long, void *), void *, long double, int, unsigned long, - unsigned long) hidden; -int stoa(int (*)(long, void *), void *, void *, unsigned long, unsigned long, - unsigned long, unsigned char, unsigned char) hidden; -int ntoa(int (*)(long, void *), void *, va_list, unsigned char, unsigned long, - unsigned long, unsigned long, unsigned char, const char *) hidden; - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_ */ diff --git a/libc/fmt/pflink.h b/libc/fmt/pflink.h index e75e1506..40f7f2af 100644 --- a/libc/fmt/pflink.h +++ b/libc/fmt/pflink.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_FMT_PFLINK_H_ #define COSMOPOLITAN_LIBC_FMT_PFLINK_H_ #include "libc/dce.h" +#include "libc/fmt/fmts.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" @@ -17,19 +18,20 @@ * format strings are constexprs that only contain directives. */ -#define PFLINK(FMT) \ - ({ \ - if (___PFLINK(FMT, strpbrk, "cmrqs")) { \ - if (___PFLINK(FMT, strchr, '#')) STATIC_YOINK("kCp437"); \ - if (___PFLINK(FMT, strstr, "%m")) STATIC_YOINK("strerror"); \ - if (!IsTiny() && (___PFLINK(FMT, strstr, "%*") || \ - ___PFLINK(FMT, strpbrk, "0123456789"))) { \ - STATIC_YOINK("strnwidth"); \ - STATIC_YOINK("strnwidth16"); \ - STATIC_YOINK("wcsnwidth"); \ - } \ - } \ - FMT; \ +#define PFLINK(FMT) \ + ({ \ + if (___PFLINK(FMT, strpbrk, "faAeEgG")) STATIC_YOINK("__fmt_dtoa"); \ + if (___PFLINK(FMT, strpbrk, "cmrqs")) { \ + if (___PFLINK(FMT, strchr, '#')) STATIC_YOINK("kCp437"); \ + if (___PFLINK(FMT, strstr, "%m")) STATIC_YOINK("strerror"); \ + if (!IsTiny() && (___PFLINK(FMT, strstr, "%*") || \ + ___PFLINK(FMT, strpbrk, "0123456789"))) { \ + STATIC_YOINK("strnwidth"); \ + STATIC_YOINK("strnwidth16"); \ + STATIC_YOINK("wcsnwidth"); \ + } \ + } \ + FMT; \ }) #define SFLINK(FMT) \ @@ -67,6 +69,7 @@ #define SFLINK(FMT) FMT #ifdef __GNUC__ __asm__(".section .yoink\n\t" + "nopl\t__fmt_dtoa(%rip)\n\t" "nopl\tkCp437(%rip)\n\t" "nopl\tstrerror(%rip)\n\t" "nopl\tstrnwidth(%rip)\n\t" @@ -79,6 +82,7 @@ __asm__(".section .yoink\n\t" #else static long __pflink(long x) { x |= kCp437[0]; + x |= __fmt_dtoa(0, 0, 0, 0, 0, 0); x |= strnwidth(0, 0, 0); x |= strnwidth16(0, 0, 0); x |= wcsnwidth(0, 0, 0); diff --git a/libc/fmt/snprintf.c b/libc/fmt/snprintf.c index afc94837..d2bbd3ea 100644 --- a/libc/fmt/snprintf.c +++ b/libc/fmt/snprintf.c @@ -24,7 +24,7 @@ * @return number of bytes written, excluding the NUL terminator; or, * if the output buffer wasn't passed, or was too short, then the * number of characters that *would* have been written is returned - * @see palandprintf() and printf() for detailed documentation + * @see __fmt() and printf() for detailed documentation * @asyncsignalsafe * @vforksafe */ diff --git a/libc/fmt/sprintf.c b/libc/fmt/sprintf.c index 25cf3af2..46174ff2 100644 --- a/libc/fmt/sprintf.c +++ b/libc/fmt/sprintf.c @@ -22,7 +22,7 @@ /** * Formats string to buffer that's hopefully large enough. * - * @see palandprintf() and printf() for detailed documentation + * @see __fmt() and printf() for detailed documentation * @see snprintf() for same w/ buf size param * @asyncsignalsafe * @vforksafe diff --git a/libc/fmt/stoa.c b/libc/fmt/stoa.c index cdbc3b59..04ae8de4 100644 --- a/libc/fmt/stoa.c +++ b/libc/fmt/stoa.c @@ -17,8 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/weaken.h" -#include "libc/fmt/paland.inc" -#include "libc/fmt/palandprintf.h" +#include "libc/fmt/fmts.h" +#include "libc/fmt/internal.h" #include "libc/nexgen32e/tinystrlen.internal.h" #include "libc/str/str.h" #include "libc/str/thompike.h" @@ -28,12 +28,13 @@ typedef int (*emit_f)(int (*)(long, void *), void *, wint_t); -static noinstrument int StoaEmitByte(int f(long, void *), void *a, wint_t c) { +static noinstrument int __fmt_stoa_byte(int f(long, void *), void *a, + wint_t c) { return f(c, a); } -static noinstrument int StoaEmitWordEncodedString(int f(long, void *), void *a, - uint64_t w) { +static noinstrument int __fmt_stoa_word(int f(long, void *), void *a, + uint64_t w) { do { if (f(w & 0xff, a) == -1) { return -1; @@ -42,31 +43,32 @@ static noinstrument int StoaEmitWordEncodedString(int f(long, void *), void *a, return 0; } -static noinstrument int StoaEmitUnicode(int f(long, void *), void *a, +static noinstrument int __fmt_stoa_wide(int f(long, void *), void *a, wint_t c) { if (isascii(c)) { return f(c, a); } else { - return StoaEmitWordEncodedString(f, a, tpenc(c)); + return __fmt_stoa_word(f, a, tpenc(c)); } } -static noinstrument int StoaEmitQuoted(int f(long, void *), void *a, wint_t c) { +static noinstrument int __fmt_stoa_bing(int f(long, void *), void *a, + wint_t c) { + return __fmt_stoa_wide(f, a, (*weaken(kCp437))[c]); +} + +static noinstrument int __fmt_stoa_quoted(int f(long, void *), void *a, + wint_t c) { if (isascii(c)) { - return StoaEmitWordEncodedString(f, a, cescapec(c)); + return __fmt_stoa_word(f, a, cescapec(c)); } else { - return StoaEmitWordEncodedString(f, a, tpenc(c)); + return __fmt_stoa_word(f, a, tpenc(c)); } } -static noinstrument int StoaEmitVisualized(int f(long, void *), void *a, - wint_t c) { - return StoaEmitUnicode(f, a, (*weaken(kCp437))[c]); -} - -static noinstrument int StoaEmitQuote(int out(long, void *), void *arg, - unsigned flags, char ch, - unsigned char signbit) { +static noinstrument int __fmt_stoa_quote(int out(long, void *), void *arg, + unsigned flags, char ch, + unsigned char signbit) { if (flags & FLAGS_REPR) { if (signbit == 63) { if (out('L', arg) == -1) return -1; @@ -81,15 +83,16 @@ static noinstrument int StoaEmitQuote(int out(long, void *), void *arg, /** * Converts string to array. * - * This function is used by palandprintf() 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. + * 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 palandprintf() + * @see __fmt() */ -int stoa(int out(long, void *), void *arg, void *data, unsigned long flags, - unsigned long precision, unsigned long width, unsigned char signbit, - unsigned char qchar) { +int __fmt_stoa(int out(long, void *), void *arg, void *data, + unsigned long flags, unsigned long precision, + unsigned long width, unsigned char signbit, + unsigned char qchar) { char *p; wint_t wc; unsigned n; @@ -104,28 +107,28 @@ int stoa(int out(long, void *), void *arg, void *data, unsigned long flags, flags |= FLAGS_NOQUOTE; signbit = 0; } else { - if (StoaEmitQuote(out, arg, flags, qchar, signbit) == -1) return -1; + if (__fmt_stoa_quote(out, arg, flags, qchar, signbit) == -1) return -1; } ignorenul = false; justdobytes = false; if (signbit == 15 || signbit == 63) { if (flags & FLAGS_QUOTE) { - emit = StoaEmitQuoted; + emit = __fmt_stoa_quoted; ignorenul = flags & FLAGS_PRECISION; } else { - emit = StoaEmitUnicode; + emit = __fmt_stoa_wide; } } else if ((flags & FLAGS_HASH) && weaken(kCp437)) { justdobytes = true; - emit = StoaEmitVisualized; + emit = __fmt_stoa_bing; ignorenul = flags & FLAGS_PRECISION; } else if (flags & FLAGS_QUOTE) { - emit = StoaEmitQuoted; + emit = __fmt_stoa_quoted; ignorenul = flags & FLAGS_PRECISION; } else { justdobytes = true; - emit = StoaEmitByte; + emit = __fmt_stoa_byte; } if (!(flags & FLAGS_PRECISION)) precision = -1; @@ -159,7 +162,7 @@ int stoa(int out(long, void *), void *arg, void *data, unsigned long flags, } if (pad && !(flags & FLAGS_LEFT)) { - if (spacepad(out, arg, pad) == -1) return -1; + if (__fmt_pad(out, arg, pad) == -1) return -1; } if (justdobytes) { @@ -208,7 +211,7 @@ int stoa(int out(long, void *), void *arg, void *data, unsigned long flags, } if (pad && (flags & FLAGS_LEFT)) { - if (spacepad(out, arg, pad) == -1) return -1; + if (__fmt_pad(out, arg, pad) == -1) return -1; } if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) { diff --git a/libc/fmt/strerror_r.c b/libc/fmt/strerror_r.c index 814869bc..4efb8b87 100644 --- a/libc/fmt/strerror_r.c +++ b/libc/fmt/strerror_r.c @@ -26,9 +26,6 @@ #include "libc/nt/runtime.h" #include "libc/str/str.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); - STATIC_YOINK("E2BIG"); STATIC_YOINK("EACCES"); STATIC_YOINK("EADDRINUSE"); diff --git a/libc/fmt/strtol.c b/libc/fmt/strtol.c index c1357eaa..00b13f96 100644 --- a/libc/fmt/strtol.c +++ b/libc/fmt/strtol.c @@ -25,9 +25,5 @@ * @param optional_base is recommended as 0 for flexidecimal */ long strtol(const char *s, char **opt_out_end, int optional_base) { - long res; - res = strtoimax(s, opt_out_end, optional_base); - if (res < LONG_MIN) return LONG_MIN; - if (res > LONG_MAX) return LONG_MAX; - return res; + return strtoimax(s, opt_out_end, optional_base); } diff --git a/libc/fmt/vsnprintf.c b/libc/fmt/vsnprintf.c index 10d51d34..72d158b9 100644 --- a/libc/fmt/vsnprintf.c +++ b/libc/fmt/vsnprintf.c @@ -44,13 +44,13 @@ static noinstrument int vsnprintfputchar(unsigned char c, * @return number of bytes written, excluding the NUL terminator; or, * if the output buffer wasn't passed, or was too short, then the * number of characters that *would* have been written is returned - * @see palandprintf() and printf() for detailed documentation + * @see __fmt() and printf() for detailed documentation * @asyncsignalsafe * @vforksafe */ int(vsnprintf)(char *buf, size_t size, const char *fmt, va_list va) { struct SprintfStr str = {buf, 0, size}; - palandprintf(vsnprintfputchar, &str, fmt, va); + __fmt(vsnprintfputchar, &str, fmt, va); if (str.n) str.p[min(str.i, str.n - 1)] = '\0'; return str.i; } diff --git a/libc/fmt/vsprintf.c b/libc/fmt/vsprintf.c index ddfce86d..fcfea86d 100644 --- a/libc/fmt/vsprintf.c +++ b/libc/fmt/vsprintf.c @@ -22,7 +22,7 @@ /** * Formats string to buffer hopefully large enough w/ vararg state. * - * @see palandprintf() and printf() for detailed documentation + * @see __fmt() and printf() for detailed documentation * @see vsnprintf() for modern alternative w/ buf size param */ int(vsprintf)(char *buf, const char *fmt, va_list va) { diff --git a/libc/fmt/wcslol.internal.h b/libc/fmt/wcslol.internal.h deleted file mode 100644 index a183fc86..00000000 --- a/libc/fmt/wcslol.internal.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_FMT_WCSLOL_H_ -#define COSMOPOLITAN_LIBC_FMT_WCSLOL_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -#define WCSLOL(STR, ENDPTR, OPTIONAL_BASE, MIN, MAX) \ - ({ \ - intmax_t res = wcstoimax(STR, ENDPTR, OPTIONAL_BASE); \ - if (res < MIN) return MIN; \ - if (res > MAX) return MAX; \ - res; \ - }) - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_FMT_WCSLOL_H_ */ diff --git a/libc/fmt/wcstol.c b/libc/fmt/wcstol.c index d3ca8260..0530dfe3 100644 --- a/libc/fmt/wcstol.c +++ b/libc/fmt/wcstol.c @@ -17,10 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/conv.h" -#include "libc/fmt/wcslol.internal.h" -#include "libc/limits.h" #include "libc/str/str.h" long wcstol(const wchar_t *s, wchar_t **end, int opt_base) { - return WCSLOL(s, end, opt_base, LONG_MIN, LONG_MAX); + return wcstoimax(s, end, opt_base); } diff --git a/libc/log/checkaligned.c b/libc/log/checkaligned.c index bd38b215..fbd5e355 100644 --- a/libc/log/checkaligned.c +++ b/libc/log/checkaligned.c @@ -22,9 +22,6 @@ #include "libc/log/log.h" #include "libc/stdio/stdio.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); - void __check_fail_aligned(unsigned bytes, uint64_t ptr) { fflush(stderr); if (!IsTiny()) memsummary(fileno(stderr)); diff --git a/libc/log/checkfail.c b/libc/log/checkfail.c index 3c6fca62..23aad9a9 100644 --- a/libc/log/checkfail.c +++ b/libc/log/checkfail.c @@ -33,10 +33,6 @@ #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/fileno.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); -STATIC_YOINK("ftoa"); - /** * Handles failure of CHECK_xx() macros. */ diff --git a/libc/log/log.mk b/libc/log/log.mk index 9418a904..f0a460e2 100644 --- a/libc/log/log.mk +++ b/libc/log/log.mk @@ -44,7 +44,8 @@ LIBC_LOG_A_DIRECTDEPS = \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_UNICODE \ - THIRD_PARTY_DLMALLOC + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_GDTOA LIBC_LOG_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_LOG_A_DIRECTDEPS),$($(x)))) diff --git a/libc/log/malloc_stats.c b/libc/log/malloc_stats.c index f9d6df0a..9369fd1a 100644 --- a/libc/log/malloc_stats.c +++ b/libc/log/malloc_stats.c @@ -20,8 +20,6 @@ #include "libc/stdio/stdio.h" #include "third_party/dlmalloc/dlmalloc.internal.h" -STATIC_YOINK("ntoa"); - void malloc_stats(void) { struct MallocStats res = dlmalloc_stats(g_dlmalloc); (fprintf)(stderr, "max system bytes = %'10zu\r\n", res.maxfp); diff --git a/libc/log/meminfo.c b/libc/log/meminfo.c index 321cbc45..95f2bf82 100644 --- a/libc/log/meminfo.c +++ b/libc/log/meminfo.c @@ -21,9 +21,6 @@ #include "libc/log/log.h" #include "libc/mem/mem.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); - static void onmemchunk(void *start, void *end, size_t used_bytes, void *arg) { (dprintf)(*(int *)arg, "%p - %p : %08zx / %08lx\r\n", start, end, used_bytes, (intptr_t)end - (intptr_t)start); diff --git a/libc/log/memsummary.c b/libc/log/memsummary.c index e5d480c1..5b3a5bef 100644 --- a/libc/log/memsummary.c +++ b/libc/log/memsummary.c @@ -20,8 +20,6 @@ #include "libc/log/log.h" #include "libc/mem/mem.h" -STATIC_YOINK("ntoa"); - void memsummary(int fd) { struct mallinfo mi; mi = mallinfo(); diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 32a3f2c5..78a9c05b 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -37,10 +37,6 @@ #define kNontrivialSize (8 * 1000 * 1000) -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); -STATIC_YOINK("ftoa"); - static struct timespec vflogf_ts; static int vflogf_loglevel2char(unsigned level) { diff --git a/libc/runtime/printmemoryintervals.c b/libc/runtime/printmemoryintervals.c index a73698e5..95cf5a76 100644 --- a/libc/runtime/printmemoryintervals.c +++ b/libc/runtime/printmemoryintervals.c @@ -20,9 +20,6 @@ #include "libc/log/log.h" #include "libc/runtime/memtrack.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); - void PrintMemoryIntervals(int fd, const struct MemoryIntervals *mm) { int i, frames, maptally, gaptally; maptally = 0; diff --git a/libc/fmt/rounddecimalplaces.c b/libc/stdio/dtoa.c similarity index 85% rename from libc/fmt/rounddecimalplaces.c rename to libc/stdio/dtoa.c index 21d972be..f58202db 100644 --- a/libc/fmt/rounddecimalplaces.c +++ b/libc/stdio/dtoa.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,13 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/conv.h" -#include "libc/math.h" +#include "libc/fmt/fmts.h" +#include "third_party/gdtoa/gdtoa.h" -double RoundDecimalPlaces(double f, double digs, double rounder(double)) { - if (!(0 <= digs && digs < 15)) { - return f; - } else { - return rounder(f * exp10(digs)) / exp10(digs); - } +char *__fmt_dtoa(double d0, int mode, int ndigits, int *decpt, int *sign, + char **rve) { + return dtoa(d0, mode, ndigits, decpt, sign, rve); } diff --git a/libc/stdio/printf.c b/libc/stdio/printf.c index de3dbb6d..8c64c6d3 100644 --- a/libc/stdio/printf.c +++ b/libc/stdio/printf.c @@ -58,7 +58,7 @@ * This means it implies the quoting modifier, wraps the value with * {,u,L}['"] quotes, displays NULL as "NULL" rather than "(null)". * - * @see palandprintf() for intuitive reference documentation + * @see __fmt() for intuitive reference documentation * @see {,v}{,s{,n},{,{,x}as},f,d}printf */ int(printf)(const char* fmt, ...) { diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h index e9598874..abd7549c 100644 --- a/libc/stdio/stdio.h +++ b/libc/stdio/stdio.h @@ -8,6 +8,9 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +int Printf(const char *, ...); +int Sprintf(char *, const char *, ...); + /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § standard i/o ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ diff --git a/libc/stdio/stdio.mk b/libc/stdio/stdio.mk index e2fb9279..a54636e9 100644 --- a/libc/stdio/stdio.mk +++ b/libc/stdio/stdio.mk @@ -37,7 +37,8 @@ LIBC_STDIO_A_DIRECTDEPS = \ LIBC_STR \ LIBC_STUBS \ LIBC_SYSV \ - LIBC_SYSV_CALLS + LIBC_SYSV_CALLS \ + THIRD_PARTY_GDTOA LIBC_STDIO_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_STDIO_A_DIRECTDEPS),$($(x)))) diff --git a/libc/stdio/vfprintf.c b/libc/stdio/vfprintf.c index d94506ad..cd2c0a2f 100644 --- a/libc/stdio/vfprintf.c +++ b/libc/stdio/vfprintf.c @@ -33,7 +33,7 @@ static noinstrument int vfprintfputchar(int c, struct state *st) { int(vfprintf)(FILE *f, const char *fmt, va_list va) { struct state st[1] = {{f, 0}}; - if (palandprintf(vfprintfputchar, st, fmt, va) != -1) { + if (__fmt(vfprintfputchar, st, fmt, va) != -1) { return st->n; } else { return -1; diff --git a/libc/testlib/ezbenchreport.c b/libc/testlib/ezbenchreport.c index 1f27b9ac..cf0e3366 100644 --- a/libc/testlib/ezbenchreport.c +++ b/libc/testlib/ezbenchreport.c @@ -21,8 +21,6 @@ #include "libc/testlib/testlib.h" #include "libc/time/time.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); STATIC_YOINK("strnwidth"); void __testlib_ezbenchreport(const char *form, uint64_t c1, uint64_t c2) { diff --git a/libc/time/asctime_r.c b/libc/time/asctime_r.c index e3e71d0c..fda926f2 100644 --- a/libc/time/asctime_r.c +++ b/libc/time/asctime_r.c @@ -20,9 +20,6 @@ #include "libc/time/struct/tm.h" #include "libc/time/time.h" -STATIC_YOINK("ntoa"); -STATIC_YOINK("stoa"); - static unsigned clip(unsigned index, unsigned count) { return index < count ? index : 0; } diff --git a/libc/time/strftime.c b/libc/time/strftime.c index 77e76e14..5a610e6d 100644 --- a/libc/time/strftime.c +++ b/libc/time/strftime.c @@ -25,8 +25,6 @@ #include "libc/time/time.h" #include "libc/time/tzfile.internal.h" -STATIC_YOINK("ntoa"); - asm(".ident\t\"\\n\\n\ strftime (BSD-3)\\n\ Copyright 1989 The Regents of the University of California\""); @@ -330,13 +328,13 @@ static char *strftime_timefmt(char *p, const char *pe, const char *format, if (t->tm_isdst == 0) #ifdef USG_COMPAT diff = -timezone; -#else /* !defined USG_COMPAT */ +#else /* !defined USG_COMPAT */ continue; #endif /* !defined USG_COMPAT */ else #ifdef ALTZONE diff = -altzone; -#else /* !defined ALTZONE */ +#else /* !defined ALTZONE */ continue; #endif /* !defined ALTZONE */ #endif /* !defined TM_GMTOFF */ diff --git a/libc/time/xiso8601.c b/libc/time/xiso8601.c index d4062d7d..3b748de9 100644 --- a/libc/time/xiso8601.c +++ b/libc/time/xiso8601.c @@ -27,9 +27,6 @@ #include "libc/time/time.h" #include "libc/x/x.h" -STATIC_YOINK("stoa"); -STATIC_YOINK("ntoa"); - /** * @fileoverview Timestamps in One True Format w/o toil. */ diff --git a/test/libc/fmt/palandprintf_test.c b/test/libc/fmt/palandprintf_test.c index 6600356d..a3b83860 100644 --- a/test/libc/fmt/palandprintf_test.c +++ b/test/libc/fmt/palandprintf_test.c @@ -433,8 +433,7 @@ TEST(sprintf, test_float) { EXPECT_STREQ("3.5", Format("%.1f", 3.49)); EXPECT_STREQ("a0.5 ", Format("a%-5.1f", 0.5)); EXPECT_STREQ("a0.5 end", Format("a%-5.1fend", 0.5)); - /* out of range in the moment, need to be fixed by someone */ - EXPECT_STREQ("inf", Format("%.1f", 1E20)); + EXPECT_STREQ("100000000000000000000.0", Format("%.1f", 1E20)); } TEST(sprintf, test_types) { diff --git a/test/libc/fmt/realfmt_test.c b/test/libc/fmt/realfmt_test.c new file mode 100644 index 00000000..05881ab5 --- /dev/null +++ b/test/libc/fmt/realfmt_test.c @@ -0,0 +1,68 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/fmt/fmt.h" +#include "libc/math.h" +#include "libc/runtime/gc.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +TEST(RealFormatting, g) { + EXPECT_STREQ("nan", gc(xasprintf("%g", NAN))); + EXPECT_STREQ("-nan", gc(xasprintf("%g", -NAN))); + EXPECT_STREQ("inf", gc(xasprintf("%g", INFINITY))); + EXPECT_STREQ("-inf", gc(xasprintf("%g", -INFINITY))); + EXPECT_STREQ("0", gc(xasprintf("%g", 0.))); + EXPECT_STREQ("-0", gc(xasprintf("%g", -0.))); + EXPECT_STREQ("1", gc(xasprintf("%g", 1.))); + EXPECT_STREQ("-1", gc(xasprintf("%g", -1.))); + EXPECT_STREQ("10", gc(xasprintf("%g", 10.))); + EXPECT_STREQ("10", gc(xasprintf("%.0g", 10.))); + EXPECT_STREQ("-10", gc(xasprintf("%g", -10.))); + EXPECT_STREQ("-10", gc(xasprintf("%.0g", -10.))); + EXPECT_STREQ(" -10", gc(xasprintf("%10g", -10.))); + EXPECT_STREQ(" -10", gc(xasprintf("%*g", 10, -10.))); + EXPECT_STREQ("1e+100", gc(xasprintf("%g", 1e100))); + EXPECT_STREQ("1e-100", gc(xasprintf("%g", 1e-100))); + EXPECT_STREQ("-1e-100", gc(xasprintf("%g", -1e-100))); + EXPECT_STREQ("3.14159", gc(xasprintf("%g", 0x1.921fb54442d1846ap+1))); +} + +TEST(RealFormatting, f) { + EXPECT_STREQ("3.141593", gc(xasprintf("%f", 0x1.921fb54442d1846ap+1))); + EXPECT_STREQ("3.1415926535897931", + gc(xasprintf("%.16f", 0x1.921fb54442d1846ap+1))); + EXPECT_STREQ("100000000000000001590289110975991804683608085639452813" + "89781327557747838772170381060813469985856815104.000000", + gc(xasprintf("%f", 1e100))); +} + +TEST(RealFormatting, e) { + EXPECT_STREQ("3.14159", gc(xasprintf("%g", 0x1.921fb54442d1846ap+1))); + EXPECT_STREQ("3.141592653589793", + gc(xasprintf("%.16g", 0x1.921fb54442d1846ap+1))); + EXPECT_STREQ("1.000000e+100", gc(xasprintf("%e", 1e100))); + EXPECT_STREQ("1.000000E+100", gc(xasprintf("%E", 1e100))); +} + +TEST(RealFormatting, a) { + EXPECT_STREQ("0x1.921fb54442d18p+1", + gc(xasprintf("%a", 0x1.921fb54442d1846ap+1))); + EXPECT_STREQ("0X1.921FB54442D18P+1", + gc(xasprintf("%A", 0x1.921fb54442d1846ap+1))); +} diff --git a/test/libc/fmt/test.mk b/test/libc/fmt/test.mk index f44902d5..808b76c3 100644 --- a/test/libc/fmt/test.mk +++ b/test/libc/fmt/test.mk @@ -29,6 +29,7 @@ TEST_LIBC_FMT_DIRECTDEPS = \ LIBC_STR \ LIBC_STUBS \ LIBC_SYSV \ + LIBC_TINYMATH \ LIBC_TESTLIB \ LIBC_UNICODE \ LIBC_X \ diff --git a/third_party/duktape/duktape.mk b/third_party/duktape/duktape.mk index cee49d28..9430677c 100644 --- a/third_party/duktape/duktape.mk +++ b/third_party/duktape/duktape.mk @@ -31,13 +31,14 @@ THIRD_PARTY_DUKTAPE_A_DIRECTDEPS = \ LIBC_FMT \ LIBC_INTRIN \ LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_RUNTIME \ + LIBC_STDIO \ LIBC_STR \ LIBC_STUBS \ LIBC_TIME \ - LIBC_RUNTIME \ LIBC_TINYMATH \ - LIBC_UNICODE \ - LIBC_NEXGEN32E + LIBC_UNICODE THIRD_PARTY_DUKTAPE_A_DEPS := \ $(call uniq,$(foreach x,$(THIRD_PARTY_DUKTAPE_A_DIRECTDEPS),$($(x)))) diff --git a/third_party/gdtoa/dtoa.c b/third_party/gdtoa/dtoa.c index 7884a8f8..da35f8b9 100644 --- a/third_party/gdtoa/dtoa.c +++ b/third_party/gdtoa/dtoa.c @@ -168,9 +168,9 @@ dtoa(double d0, int mode, int ndigits, int *decpt, int *sign, char **rve) *decpt = 9999; #ifdef IEEE_Arith if (!word1(&d) && !(word0(&d) & 0xfffff)) - return nrv_alloc("Infinity", rve, 8 MTb); + return nrv_alloc("inf", rve, 8 MTb); #endif - return nrv_alloc("NaN", rve, 3 MTb); + return nrv_alloc("nan", rve, 3 MTb); } #endif #ifdef IBM diff --git a/third_party/getopt/getopt.c b/third_party/getopt/getopt.c index c6426140..dee19a62 100644 --- a/third_party/getopt/getopt.c +++ b/third_party/getopt/getopt.c @@ -135,8 +135,8 @@ int getopt(int nargc, char *const nargv[], const char *ostr) { if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { if (*getopt_place == 0) ++optind; if (opterr && *ostr != ':') { - fprintf(stderr, "%s: illegal option -- %c\n", program_invocation_name, - optopt); + fprintf(stderr, "%s%s%c\n", program_invocation_name, + ": illegal option -- ", optopt); } return (BADCH); } @@ -157,8 +157,8 @@ int getopt(int nargc, char *const nargv[], const char *ostr) { getopt_place = kGetoptEmsg; if (*ostr == ':') return (BADARG); if (opterr) - fprintf(stderr, "%s: option requires an argument -- %c\n", - program_invocation_name, optopt); + fprintf(stderr, "%s%s%c\n", program_invocation_name, + ": option requires an argument -- ", optopt); return (BADCH); } getopt_place = kGetoptEmsg;