/** @file | |
Implementation of internals for printf and wprintf. | |
Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR> | |
This program and the accompanying materials are licensed and made available | |
under the terms and conditions of the BSD License that accompanies this | |
distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license. | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
Copyright (c) 1990, 1993 | |
The Regents of the University of California. All rights reserved. | |
This code is derived from software contributed to Berkeley by | |
Chris Torek. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
- Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
- Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
- Neither the name of the University nor the names of its contributors | |
may be used to endorse or promote products derived from this software | |
without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE | |
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
POSSIBILITY OF SUCH DAMAGE. | |
NetBSD: vfwprintf.c,v 1.9.2.1.4.1 2008/04/08 21:10:55 jdc Exp | |
vfprintf.c 8.1 (Berkeley) 6/4/93 | |
**/ | |
#include <LibConfig.h> | |
#include "namespace.h" | |
#include <sys/types.h> | |
#include <assert.h> | |
#include <ctype.h> | |
#include <limits.h> | |
#include <locale.h> | |
#include <stdarg.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <wchar.h> | |
#include <wctype.h> | |
#include "reentrant.h" | |
#include "local.h" | |
#include "extern.h" | |
#include "fvwrite.h" | |
#ifdef _MSC_VER | |
// Keep compiler quiet about conversions from larger to smaller types. | |
#pragma warning ( disable : 4244 ) | |
#endif | |
#ifndef NARROW | |
#define MCHAR_T char | |
#define CHAR_T wchar_t | |
#define STRLEN(a) wcslen(a) | |
#define MEMCHR(a, b, c) wmemchr(a, b, c) | |
#define SCONV(a, b) __mbsconv(a, b) | |
#define STRCONST(a) L ## a | |
#define WDECL(a, b) a ## w ## b | |
#define END_OF_FILE WEOF | |
#define MULTI 0 | |
#else | |
#define MCHAR_T wchar_t | |
#define CHAR_T char | |
#define STRLEN(a) strlen(a) | |
#define MEMCHR(a, b, c) memchr(a, b, c) | |
#define SCONV(a, b) __wcsconv(a, b) | |
#define STRCONST(a) a | |
#define WDECL(a, b) a ## b | |
#define END_OF_FILE EOF | |
#define MULTI 1 | |
#endif | |
union arg { | |
int intarg; | |
u_int uintarg; | |
long longarg; | |
unsigned long ulongarg; | |
long long longlongarg; | |
unsigned long long ulonglongarg; | |
ptrdiff_t ptrdiffarg; | |
size_t sizearg; | |
intmax_t intmaxarg; | |
uintmax_t uintmaxarg; | |
void *pvoidarg; | |
char *pchararg; | |
signed char *pschararg; | |
short *pshortarg; | |
int *pintarg; | |
long *plongarg; | |
long long *plonglongarg; | |
ptrdiff_t *pptrdiffarg; | |
size_t *psizearg; | |
intmax_t *pintmaxarg; | |
#ifndef NO_FLOATING_POINT | |
double doublearg; | |
long double longdoublearg; | |
#endif | |
wint_t wintarg; | |
wchar_t *pwchararg; | |
}; | |
/* | |
* Type ids for argument type table. | |
*/ | |
enum typeid { | |
T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT, | |
T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, | |
TP_LLONG, T_PTRDIFFT, TP_PTRDIFFT, T_SIZET, TP_SIZET, | |
T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID, TP_CHAR, | |
TP_SCHAR, T_DOUBLE, T_LONG_DOUBLE, T_WINT, TP_WCHAR | |
}; | |
static int __sbprintf(FILE *, const CHAR_T *, va_list); | |
static CHAR_T *__ujtoa(uintmax_t, CHAR_T *, int, int, const char *, int, | |
char, const char *); | |
static CHAR_T *__ultoa(u_long, CHAR_T *, int, int, const char *, int, | |
char, const char *); | |
#ifndef NARROW | |
static CHAR_T *__mbsconv(char *, int); | |
static wint_t __xfputwc(CHAR_T, FILE *); | |
#else | |
static char *__wcsconv(wchar_t *, int); | |
static int __sprint(FILE *, struct __suio *); | |
#endif | |
static int __find_arguments(const CHAR_T *, va_list, union arg **); | |
static int __grow_type_table(int, enum typeid **, int *); | |
/* | |
* Helper function for `fprintf to unbuffered unix file': creates a | |
* temporary buffer. We only work on write-only files; this avoids | |
* worries about ungetc buffers and so forth. | |
*/ | |
static int | |
__sbprintf(FILE *fp, const CHAR_T *fmt, va_list ap) | |
{ | |
int ret; | |
FILE fake; | |
struct __sfileext fakeext; | |
unsigned char buf[BUFSIZ]; | |
_DIAGASSERT(fp != NULL); | |
_DIAGASSERT(fmt != NULL); | |
if(fp == NULL) { | |
errno = EINVAL; | |
return (EOF); | |
} | |
_FILEEXT_SETUP(&fake, &fakeext); | |
/* copy the important variables */ | |
fake._flags = fp->_flags & ~__SNBF; | |
fake._file = fp->_file; | |
fake._cookie = fp->_cookie; | |
fake._write = fp->_write; | |
/* set up the buffer */ | |
fake._bf._base = fake._p = buf; | |
fake._bf._size = fake._w = sizeof(buf); | |
fake._lbfsize = 0; /* not actually used, but Just In Case */ | |
/* do the work, then copy any error status */ | |
ret = WDECL(__vf,printf_unlocked)(&fake, fmt, ap); | |
if (ret >= 0 && fflush(&fake)) | |
ret = END_OF_FILE; | |
if (fake._flags & __SERR) | |
fp->_flags |= __SERR; | |
return (ret); | |
} | |
#ifndef NARROW | |
/* | |
* Like __fputwc, but handles fake string (__SSTR) files properly. | |
* File must already be locked. | |
*/ | |
static wint_t | |
__xfputwc(wchar_t wc, FILE *fp) | |
{ | |
static const mbstate_t initial = { 0 }; | |
mbstate_t mbs; | |
char buf[MB_LEN_MAX]; | |
struct __suio uio; | |
struct __siov iov; | |
size_t len; | |
if ((fp->_flags & __SSTR) == 0) | |
return (__fputwc_unlock(wc, fp)); | |
mbs = initial; | |
if ((len = wcrtomb(buf, wc, &mbs)) == (size_t)-1) { | |
fp->_flags |= __SERR; | |
return (END_OF_FILE); | |
} | |
uio.uio_iov = &iov; | |
uio.uio_resid = (int)len; | |
uio.uio_iovcnt = 1; | |
iov.iov_base = buf; | |
iov.iov_len = len; | |
return (__sfvwrite(fp, &uio) != EOF ? (wint_t)wc : END_OF_FILE); | |
} | |
#else | |
/* | |
* Flush out all the vectors defined by the given uio, | |
* then reset it so that it can be reused. | |
*/ | |
static int | |
__sprint(FILE *fp, struct __suio *uio) | |
{ | |
int err; | |
_DIAGASSERT(fp != NULL); | |
_DIAGASSERT(uio != NULL); | |
if(fp == NULL) { | |
errno = EINVAL; | |
return (EOF); | |
} | |
if (uio->uio_resid == 0) { | |
uio->uio_iovcnt = 0; | |
return (0); | |
} | |
err = __sfvwrite(fp, uio); | |
uio->uio_resid = 0; | |
uio->uio_iovcnt = 0; | |
return (err); | |
} | |
#endif | |
/* | |
* Macros for converting digits to letters and vice versa | |
*/ | |
#define to_digit(c) ((c) - '0') | |
#define is_digit(c) ((unsigned)to_digit(c) <= 9) | |
#define to_char(n) (CHAR_T)((n) + '0') | |
/* | |
* Convert an unsigned long to ASCII for printf purposes, returning | |
* a pointer to the first character of the string representation. | |
* Octal numbers can be forced to have a leading zero; hex numbers | |
* use the given digits. | |
*/ | |
static CHAR_T * | |
__ultoa(u_long val, CHAR_T *endp, int base, int octzero, const char *xdigs, | |
int needgrp, char thousep, const char *grp) | |
{ | |
CHAR_T *cp = endp; | |
LONGN sval; | |
int ndig; | |
/* | |
* Handle the three cases separately, in the hope of getting | |
* better/faster code. | |
*/ | |
switch (base) { | |
case 10: | |
if (val < 10) { /* many numbers are 1 digit */ | |
*--cp = to_char(val); | |
return (cp); | |
} | |
ndig = 0; | |
/* | |
* On many machines, unsigned arithmetic is harder than | |
* signed arithmetic, so we do at most one unsigned mod and | |
* divide; this is sufficient to reduce the range of | |
* the incoming value to where signed arithmetic works. | |
*/ | |
if (val > LONG_MAX) { | |
*--cp = to_char(val % 10); | |
ndig++; | |
sval = (LONGN)(val / 10); | |
} else | |
sval = (LONGN)val; | |
do { | |
*--cp = to_char(sval % 10); | |
ndig++; | |
/* | |
* If (*grp == CHAR_MAX) then no more grouping | |
* should be performed. | |
*/ | |
if (needgrp && ndig == *grp && *grp != CHAR_MAX | |
&& sval > 9) { | |
*--cp = thousep; | |
ndig = 0; | |
/* | |
* If (*(grp+1) == '\0') then we have to | |
* use *grp character (last grouping rule) | |
* for all next cases | |
*/ | |
if (*(grp+1) != '\0') | |
grp++; | |
} | |
sval /= 10; | |
} while (sval != 0); | |
break; | |
case 8: | |
do { | |
*--cp = to_char(val & 7); | |
val >>= 3; | |
} while (val); | |
if (octzero && *cp != '0') | |
*--cp = '0'; | |
break; | |
case 16: | |
do { | |
*--cp = xdigs[(size_t)val & 15]; | |
val >>= 4; | |
} while (val); | |
break; | |
default: /* oops */ | |
abort(); | |
} | |
return (cp); | |
} | |
/* Identical to __ultoa, but for intmax_t. */ | |
static CHAR_T * | |
__ujtoa(uintmax_t val, CHAR_T *endp, int base, int octzero, | |
const char *xdigs, int needgrp, char thousep, const char *grp) | |
{ | |
CHAR_T *cp = endp; | |
intmax_t sval; | |
int ndig; | |
/* quick test for small values; __ultoa is typically much faster */ | |
/* (perhaps instead we should run until small, then call __ultoa?) */ | |
if (val <= ULONG_MAX) | |
return (__ultoa((u_long)val, endp, base, octzero, xdigs, | |
needgrp, thousep, grp)); | |
switch (base) { | |
case 10: | |
if (val < 10) { | |
*--cp = to_char(val % 10); | |
return (cp); | |
} | |
ndig = 0; | |
if (val > INTMAX_MAX) { | |
*--cp = to_char(val % 10); | |
ndig++; | |
sval = val / 10; | |
} else | |
sval = val; | |
do { | |
*--cp = to_char(sval % 10); | |
ndig++; | |
/* | |
* If (*grp == CHAR_MAX) then no more grouping | |
* should be performed. | |
*/ | |
if (needgrp && *grp != CHAR_MAX && ndig == *grp | |
&& sval > 9) { | |
*--cp = thousep; | |
ndig = 0; | |
/* | |
* If (*(grp+1) == '\0') then we have to | |
* use *grp character (last grouping rule) | |
* for all next cases | |
*/ | |
if (*(grp+1) != '\0') | |
grp++; | |
} | |
sval /= 10; | |
} while (sval != 0); | |
break; | |
case 8: | |
do { | |
*--cp = to_char(val & 7); | |
val >>= 3; | |
} while (val); | |
if (octzero && *cp != '0') | |
*--cp = '0'; | |
break; | |
case 16: | |
do { | |
*--cp = xdigs[(size_t)val & 15]; | |
val >>= 4; | |
} while (val); | |
break; | |
default: | |
abort(); | |
} | |
return (cp); | |
} | |
#ifndef NARROW | |
/* | |
* Convert a multibyte character string argument for the %s format to a wide | |
* string representation. ``prec'' specifies the maximum number of bytes | |
* to output. If ``prec'' is greater than or equal to zero, we can't assume | |
* that the multibyte char. string ends in a null character. | |
*/ | |
static wchar_t * | |
__mbsconv(char *mbsarg, int prec) | |
{ | |
static const mbstate_t initial = { 0 }; | |
mbstate_t mbs; | |
wchar_t *convbuf, *wcp; | |
const char *p; | |
size_t insize, nchars, nconv; | |
if (mbsarg == NULL) | |
return (NULL); | |
/* | |
* Supplied argument is a multibyte string; convert it to wide | |
* characters first. | |
*/ | |
if (prec >= 0) { | |
/* | |
* String is not guaranteed to be NUL-terminated. Find the | |
* number of characters to print. | |
*/ | |
p = mbsarg; | |
insize = nchars = nconv = 0; | |
mbs = initial; | |
while (nchars != (size_t)prec) { | |
nconv = mbrlen(p, MB_CUR_MAX, &mbs); | |
if (nconv == 0 || nconv == (size_t)-1 || | |
nconv == (size_t)-2) | |
break; | |
p += nconv; | |
nchars++; | |
insize += nconv; | |
} | |
if (nconv == (size_t)-1 || nconv == (size_t)-2) | |
return (NULL); | |
} else | |
insize = strlen(mbsarg); | |
/* | |
* Allocate buffer for the result and perform the conversion, | |
* converting at most `size' bytes of the input multibyte string to | |
* wide characters for printing. | |
*/ | |
convbuf = malloc((insize + 1) * sizeof(*convbuf)); | |
if (convbuf == NULL) | |
return (NULL); | |
wcp = convbuf; | |
p = mbsarg; | |
mbs = initial; | |
nconv = 0; | |
while (insize != 0) { | |
nconv = mbrtowc(wcp, p, insize, &mbs); | |
if (nconv == 0 || nconv == (size_t)-1 || nconv == (size_t)-2) | |
break; | |
wcp++; | |
p += nconv; | |
insize -= nconv; | |
} | |
if (nconv == (size_t)-1 || nconv == (size_t)-2) { | |
free(convbuf); | |
return (NULL); | |
} | |
*wcp = L'\0'; | |
return (convbuf); | |
} | |
#else | |
/* | |
* Convert a wide character string argument for the %ls format to a multibyte | |
* string representation. If not -1, prec specifies the maximum number of | |
* bytes to output, and also means that we can't assume that the wide char. | |
* string ends is null-terminated. | |
*/ | |
static char * | |
__wcsconv(wchar_t *wcsarg, int prec) | |
{ | |
static const mbstate_t initial = { 0 }; | |
mbstate_t mbs; | |
char buf[MB_LEN_MAX]; | |
wchar_t *p; | |
char *convbuf; | |
size_t clen, nbytes; | |
/* Allocate space for the maximum number of bytes we could output. */ | |
if (prec < 0) { | |
p = wcsarg; | |
mbs = initial; | |
nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs); | |
if (nbytes == (size_t)-1) | |
return (NULL); | |
} else { | |
/* | |
* Optimisation: if the output precision is small enough, | |
* just allocate enough memory for the maximum instead of | |
* scanning the string. | |
*/ | |
if (prec < 128) | |
nbytes = prec; | |
else { | |
nbytes = 0; | |
p = wcsarg; | |
mbs = initial; | |
for (;;) { | |
clen = wcrtomb(buf, *p++, &mbs); | |
if (clen == 0 || clen == (size_t)-1 || | |
nbytes + clen > (size_t)prec) | |
break; | |
nbytes += clen; | |
} | |
} | |
} | |
if ((convbuf = malloc(nbytes + 1)) == NULL) | |
return (NULL); | |
/* Fill the output buffer. */ | |
p = wcsarg; | |
mbs = initial; | |
if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p, | |
nbytes, &mbs)) == (size_t)-1) { | |
free(convbuf); | |
return (NULL); | |
} | |
convbuf[nbytes] = '\0'; | |
return (convbuf); | |
} | |
#endif | |
/* | |
* MT-safe version | |
*/ | |
int | |
WDECL(vf,printf)(FILE * __restrict fp, const CHAR_T * __restrict fmt0, va_list ap) | |
{ | |
int ret; | |
if(fp == NULL) { | |
errno = EINVAL; | |
return (EOF); | |
} | |
FLOCKFILE(fp); | |
ret = WDECL(__vf,printf_unlocked)(fp, fmt0, ap); | |
FUNLOCKFILE(fp); | |
return (ret); | |
} | |
#ifndef NO_FLOATING_POINT | |
#include <float.h> | |
#include <math.h> | |
#include "floatio.h" | |
#define DEFPREC 6 | |
static int exponent(CHAR_T *, int, int); | |
#ifndef WIDE_DOUBLE | |
static char *cvt(double, int, int, char *, int *, int, int *); | |
#endif | |
#endif /* !NO_FLOATING_POINT */ | |
/* | |
* The size of the buffer we use as scratch space for integer | |
* conversions, among other things. Technically, we would need the | |
* most space for base 10 conversions with thousands' grouping | |
* characters between each pair of digits. 100 bytes is a | |
* conservative overestimate even for a 128-bit uintmax_t. | |
*/ | |
#define BUF 100 | |
#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */ | |
/* | |
* Flags used during conversion. | |
*/ | |
#define ALT 0x001 /* alternate form */ | |
#define LADJUST 0x004 /* left adjustment */ | |
#define LONGDBL 0x008 /* long double */ | |
#define LONGINT 0x010 /* long integer */ | |
#define LLONGINT 0x020 /* long long integer */ | |
#define SHORTINT 0x040 /* short integer */ | |
#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */ | |
#define FPT 0x100 /* Floating point number */ | |
#define GROUPING 0x200 /* use grouping ("'" flag) */ | |
/* C99 additional size modifiers: */ | |
#define SIZET 0x400 /* size_t */ | |
#define PTRDIFFT 0x800 /* ptrdiff_t */ | |
#define INTMAXT 0x1000 /* intmax_t */ | |
#define CHARINT 0x2000 /* print char using int format */ | |
/* | |
* Non-MT-safe version | |
*/ | |
int | |
WDECL(__vf,printf_unlocked)(FILE *fp, const CHAR_T *fmt0, va_list ap) | |
{ | |
CHAR_T *fmt; /* format string */ | |
int ch; /* character from fmt */ | |
int n, n2; /* handy integer (short term usage) */ | |
CHAR_T *cp; /* handy char pointer (short term usage) */ | |
int flags; /* flags as above */ | |
int ret; /* return value accumulator (number of items converted)*/ | |
int width; /* width from format (%8d), or 0 */ | |
int prec; /* precision from format; <0 for N/A */ | |
CHAR_T sign; /* sign prefix (' ', '+', '-', or \0) */ | |
char thousands_sep; /* locale specific thousands separator */ | |
const char *grouping; /* locale specific numeric grouping rules */ | |
#ifndef NO_FLOATING_POINT | |
/* | |
* We can decompose the printed representation of floating | |
* point numbers into several parts, some of which may be empty: | |
* | |
* [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ | |
* A B ---C--- D E F | |
* | |
* A: 'sign' holds this value if present; '\0' otherwise | |
* B: ox[1] holds the 'x' or 'X'; '\0' if not hexadecimal | |
* C: cp points to the string MMMNNN. Leading and trailing | |
* zeros are not in the string and must be added. | |
* D: expchar holds this character; '\0' if no exponent, e.g. %f | |
* F: at least two digits for decimal, at least one digit for hex | |
*/ | |
char *decimal_point; /* locale specific decimal point */ | |
#ifdef WIDE_DOUBLE | |
int signflag; /* true if float is negative */ | |
union { /* floating point arguments %[aAeEfFgG] */ | |
double dbl; | |
long double ldbl; | |
} fparg; | |
char *dtoaend; /* pointer to end of converted digits */ | |
#else | |
double _double; /* double precision arguments %[eEfgG] */ | |
char softsign; /* temporary negative sign for floats */ | |
#endif | |
char *dtoaresult; /* buffer allocated by dtoa */ | |
int expt = 0; /* integer value of exponent */ | |
char expchar; /* exponent character: [eEpP\0] */ | |
int expsize; /* character count for expstr */ | |
int lead; /* sig figs before decimal or group sep */ | |
int ndig; /* actual number of digits returned by dtoa */ | |
CHAR_T expstr[MAXEXPDIG+2]; /* buffer for exponent string: e+ZZZ */ | |
int nseps; /* number of group separators with ' */ | |
int nrepeats; /* number of repeats of the last group */ | |
#endif | |
u_long ulval; /* integer arguments %[diouxX] */ | |
uintmax_t ujval; /* %j, %ll, %q, %t, %z integers */ | |
int base; /* base for [diouxX] conversion */ | |
int dprec; /* a copy of prec if [diouxX], 0 otherwise */ | |
int realsz; /* field size expanded by dprec, sign, etc */ | |
int size; /* size of converted field or string */ | |
int prsize; /* max size of printed field */ | |
const char *xdigs; /* digits for %[xX] conversion */ | |
#ifdef NARROW | |
#define NIOV 8 | |
struct __siov *iovp; /* for PRINT macro */ | |
struct __suio uio; /* output information: summary */ | |
struct __siov iov[NIOV];/* ... and individual io vectors */ | |
#else | |
int n3; | |
#endif | |
CHAR_T buf[BUF]; /* buffer with space for digits of uintmax_t */ | |
CHAR_T ox[2]; /* space for 0x hex-prefix */ | |
union arg *argtable; /* args, built due to positional arg */ | |
union arg statargtable [STATIC_ARG_TBL_SIZE]; | |
int nextarg; /* 1-based argument index */ | |
va_list orgap; /* original argument pointer */ | |
CHAR_T *convbuf; /* multibyte to wide conversion result */ | |
/* | |
* Choose PADSIZE to trade efficiency vs. size. If larger printf | |
* fields occur frequently, increase PADSIZE and make the initialisers | |
* below longer. | |
*/ | |
#define PADSIZE 16 /* pad chunk size */ | |
static CHAR_T blanks[PADSIZE] = | |
{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; | |
static CHAR_T zeroes[PADSIZE] = | |
{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; | |
static const char xdigs_lower[17] = "0123456789abcdef"; | |
static const char xdigs_upper[17] = "0123456789ABCDEF"; | |
/* | |
* BEWARE, these `goto error' on error, PRINT uses `n2' and | |
* PAD uses `n'. | |
*/ | |
#ifndef NARROW | |
#define PRINT(ptr, len) do { \ | |
for (n3 = 0; n3 < (len); n3++) \ | |
__xfputwc((ptr)[n3], fp); \ | |
} while (/*CONSTCOND*/0) | |
#define FLUSH() | |
#else | |
#define PRINT(ptr, len) do { \ | |
iovp->iov_base = __UNCONST(ptr); \ | |
iovp->iov_len = (len); \ | |
uio.uio_resid += (len); \ | |
iovp++; \ | |
if (++uio.uio_iovcnt >= NIOV) { \ | |
if (__sprint(fp, &uio)) \ | |
goto error; \ | |
iovp = iov; \ | |
} \ | |
} while (/*CONSTCOND*/0) | |
#define FLUSH() do { \ | |
if (uio.uio_resid && __sprint(fp, &uio)) \ | |
goto error; \ | |
uio.uio_iovcnt = 0; \ | |
iovp = iov; \ | |
} while (/*CONSTCOND*/0) | |
#endif /* NARROW */ | |
#define PAD(howmany, with) do { \ | |
if ((n = (howmany)) > 0) { \ | |
while (n > PADSIZE) { \ | |
PRINT(with, PADSIZE); \ | |
n -= PADSIZE; \ | |
} \ | |
PRINT(with, n); \ | |
} \ | |
} while (/*CONSTCOND*/0) | |
#define PRINTANDPAD(p, ep, len, with) do { \ | |
n2 = (ep) - (p); \ | |
if (n2 > (len)) \ | |
n2 = (len); \ | |
if (n2 > 0) \ | |
PRINT((p), n2); \ | |
PAD((len) - (n2 > 0 ? n2 : 0), (with)); \ | |
} while(/*CONSTCOND*/0) | |
/* | |
* Get the argument indexed by nextarg. If the argument table is | |
* built, use it to get the argument. If its not, get the next | |
* argument (and arguments must be gotten sequentially). | |
*/ | |
#define GETARG(type) \ | |
((/*CONSTCOND*/argtable != NULL) ? *((type*)(void*)(&argtable[nextarg++])) : \ | |
(nextarg++, va_arg(ap, type))) | |
/* | |
* To extend shorts properly, we need both signed and unsigned | |
* argument extraction methods. | |
*/ | |
#define SARG() \ | |
((long)(flags&LONGINT ? GETARG(long) : \ | |
flags&SHORTINT ? (short)GETARG(int) : \ | |
flags&CHARINT ? (signed char)GETARG(int) : \ | |
GETARG(int))) | |
#define UARG() \ | |
((u_long)(flags&LONGINT ? GETARG(u_long) : \ | |
flags&SHORTINT ? (u_long)(u_short)GETARG(int) : \ | |
flags&CHARINT ? (u_long)(u_char)GETARG(int) : \ | |
(u_long)GETARG(u_int))) | |
#define INTMAX_SIZE (INTMAXT|SIZET|PTRDIFFT|LLONGINT) | |
#define SJARG() \ | |
(flags&INTMAXT ? GETARG(intmax_t) : \ | |
flags&SIZET ? (intmax_t)GETARG(size_t) : \ | |
flags&PTRDIFFT ? (intmax_t)GETARG(ptrdiff_t) : \ | |
(intmax_t)GETARG(long long)) | |
#define UJARG() \ | |
(flags&INTMAXT ? GETARG(uintmax_t) : \ | |
flags&SIZET ? (uintmax_t)GETARG(size_t) : \ | |
flags&PTRDIFFT ? (uintmax_t)GETARG(ptrdiff_t) : \ | |
(uintmax_t)GETARG(unsigned long long)) | |
/* | |
* Get * arguments, including the form *nn$. Preserve the nextarg | |
* that the argument can be gotten once the type is determined. | |
*/ | |
#define GETASTER(val) \ | |
n2 = 0; \ | |
cp = fmt; \ | |
while (is_digit(*cp)) { \ | |
n2 = 10 * n2 + to_digit(*cp); \ | |
cp++; \ | |
} \ | |
if (*cp == '$') { \ | |
int hold = nextarg; \ | |
if (argtable == NULL) { \ | |
argtable = statargtable; \ | |
if (__find_arguments(fmt0, orgap, &argtable) == -1) \ | |
goto oomem; \ | |
} \ | |
nextarg = n2; \ | |
val = GETARG (int); \ | |
nextarg = hold; \ | |
fmt = ++cp; \ | |
} else { \ | |
val = GETARG (int); \ | |
} | |
_DIAGASSERT(fp != NULL); | |
_DIAGASSERT(fmt0 != NULL); | |
if(fp == NULL) { | |
errno = EINVAL; | |
return (EOF); | |
} | |
_SET_ORIENTATION(fp, -1); | |
ndig = -1; /* XXX gcc */ | |
thousands_sep = '\0'; | |
grouping = NULL; | |
#ifndef NO_FLOATING_POINT | |
decimal_point = localeconv()->decimal_point; | |
expsize = 0; /* XXXGCC -Wuninitialized [sh3,m68000] */ | |
#endif | |
convbuf = NULL; | |
/* sorry, f{w,}printf(read_only_file, L"") returns {W,}EOF, not 0 */ | |
if (cantwrite(fp)) { | |
errno = EBADF; | |
return (END_OF_FILE); | |
} | |
/* optimise fprintf(stderr) (and other unbuffered Unix files) */ | |
if ((fp->_flags & (__SNBF|__SWR|__SRW)) == (__SNBF|__SWR) && | |
fp->_file >= 0) | |
return (__sbprintf(fp, fmt0, ap)); | |
fmt = (CHAR_T *)__UNCONST(fmt0); | |
argtable = NULL; | |
nextarg = 1; | |
va_copy(orgap, ap); | |
#ifdef NARROW | |
uio.uio_iov = iovp = iov; | |
uio.uio_resid = 0; | |
uio.uio_iovcnt = 0; | |
#endif | |
ret = 0; | |
/* | |
* Scan the format for conversions (`%' character). | |
*/ | |
for (;;) | |
{ | |
const CHAR_T *result; | |
for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) | |
continue; | |
if ((n = (int)(fmt - cp)) != 0) { | |
if ((unsigned)ret + n > INT_MAX) { | |
ret = END_OF_FILE; | |
goto error; | |
} | |
PRINT(cp, n); | |
ret += n; | |
} | |
if (ch == '\0') | |
goto done; | |
fmt++; /* skip over '%' */ | |
flags = 0; | |
dprec = 0; | |
width = 0; | |
prec = -1; | |
sign = '\0'; | |
ox[1] = '\0'; | |
expchar = '\0'; | |
lead = 0; | |
nseps = nrepeats = 0; | |
ulval = 0; | |
ujval = 0; | |
xdigs = NULL; | |
rflag: ch = *fmt++; | |
reswitch: switch (ch) { | |
case ' ': | |
/*- | |
* ``If the space and + flags both appear, the space | |
* flag will be ignored.'' | |
* -- ANSI X3J11 | |
*/ | |
if (!sign) | |
sign = ' '; | |
goto rflag; | |
case '#': | |
flags |= ALT; | |
goto rflag; | |
case '*': | |
/*- | |
* ``A negative field width argument is taken as a | |
* - flag followed by a positive field width.'' | |
* -- ANSI X3J11 | |
* They don't exclude field widths read from args. | |
*/ | |
GETASTER (width); | |
if (width >= 0) | |
goto rflag; | |
width = -width; | |
/* FALLTHROUGH */ | |
case '-': | |
flags |= LADJUST; | |
goto rflag; | |
case '+': | |
sign = '+'; | |
goto rflag; | |
case '\'': | |
flags |= GROUPING; | |
thousands_sep = *(localeconv()->thousands_sep); | |
grouping = localeconv()->grouping; | |
goto rflag; | |
case '.': | |
if ((ch = *fmt++) == '*') { | |
GETASTER (prec); | |
goto rflag; | |
} | |
prec = 0; | |
while (is_digit(ch)) { | |
prec = 10 * prec + to_digit(ch); | |
ch = *fmt++; | |
} | |
goto reswitch; | |
case '0': | |
/*- | |
* ``Note that 0 is taken as a flag, not as the | |
* beginning of a field width.'' | |
* -- ANSI X3J11 | |
*/ | |
flags |= ZEROPAD; | |
goto rflag; | |
case '1': case '2': case '3': case '4': | |
case '5': case '6': case '7': case '8': case '9': | |
n = 0; | |
do { | |
n = 10 * n + to_digit(ch); | |
ch = *fmt++; | |
} while (is_digit(ch)); | |
if (ch == '$') { | |
nextarg = n; | |
if (argtable == NULL) { | |
argtable = statargtable; | |
if (__find_arguments(fmt0, orgap, | |
&argtable) == -1) | |
goto oomem; | |
} | |
goto rflag; | |
} | |
width = n; | |
goto reswitch; | |
#ifndef NO_FLOATING_POINT | |
case 'L': | |
flags |= LONGDBL; | |
goto rflag; | |
#endif | |
case 'h': | |
if (flags & SHORTINT) { | |
flags &= ~SHORTINT; | |
flags |= CHARINT; | |
} else | |
flags |= SHORTINT; | |
goto rflag; | |
case 'j': | |
flags |= INTMAXT; | |
goto rflag; | |
case 'l': | |
if (flags & LONGINT) { | |
flags &= ~LONGINT; | |
flags |= LLONGINT; | |
} else | |
flags |= LONGINT; | |
goto rflag; | |
case 'q': | |
flags |= LLONGINT; /* not necessarily */ | |
goto rflag; | |
case 't': | |
flags |= PTRDIFFT; | |
goto rflag; | |
case 'z': | |
flags |= SIZET; | |
goto rflag; | |
case 'C': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'c': | |
#ifdef NARROW | |
if (flags & LONGINT) { | |
static const mbstate_t initial = { 0 }; | |
mbstate_t mbs; | |
size_t mbseqlen; | |
mbs = initial; | |
mbseqlen = wcrtomb(buf, | |
/* The compiler "knows" that wint_t may be smaller than an int so | |
it warns about it when used as the type argument to va_arg(). | |
Since any type of parameter smaller than an int is promoted to an int on a | |
function call, we must call GETARG with type int instead of wint_t. | |
*/ | |
(wchar_t)GETARG(int), &mbs); | |
if (mbseqlen == (size_t)-1) { | |
fp->_flags |= __SERR; | |
goto error; | |
} | |
size = (int)mbseqlen; | |
} else { | |
*buf = (char)(GETARG(int)); | |
size = 1; | |
} | |
#else | |
if (flags & LONGINT) | |
*buf = (wchar_t)GETARG(int); | |
else | |
*buf = (wchar_t)btowc(GETARG(int)); | |
size = 1; | |
#endif | |
result = buf; | |
sign = '\0'; | |
break; | |
case 'D': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'd': | |
case 'i': | |
if (flags & INTMAX_SIZE) { | |
ujval = SJARG(); | |
if ((intmax_t)ujval < 0) { | |
ujval = (uintmax_t)(-((intmax_t)ujval)); | |
sign = '-'; | |
} | |
} else { | |
ulval = SARG(); | |
if ((long)ulval < 0) { | |
ulval = (u_long)(-((long)ulval)); | |
sign = '-'; | |
} | |
} | |
base = 10; | |
goto number; | |
#ifndef NO_FLOATING_POINT | |
#ifdef WIDE_DOUBLE | |
case 'a': | |
case 'A': | |
if (ch == 'a') { | |
ox[1] = 'x'; | |
xdigs = xdigs_lower; | |
expchar = 'p'; | |
} else { | |
ox[1] = 'X'; | |
xdigs = xdigs_upper; | |
expchar = 'P'; | |
} | |
if (flags & LONGDBL) { | |
fparg.ldbl = GETARG(long double); | |
dtoaresult = | |
__hldtoa(fparg.ldbl, xdigs, prec, | |
&expt, &signflag, &dtoaend); | |
} else { | |
fparg.dbl = GETARG(double); | |
dtoaresult = | |
__hdtoa(fparg.dbl, xdigs, prec, | |
&expt, &signflag, &dtoaend); | |
} | |
if (dtoaresult == NULL) | |
goto oomem; | |
if (prec < 0) | |
prec = dtoaend - dtoaresult; | |
if (expt == INT_MAX) | |
ox[1] = '\0'; | |
ndig = dtoaend - dtoaresult; | |
if (convbuf != NULL) | |
free(convbuf); | |
#ifndef NARROW | |
result = convbuf = __mbsconv(dtoaresult, -1); | |
#else | |
/*XXX inefficient*/ | |
result = convbuf = strdup(dtoaresult); | |
#endif | |
if (result == NULL) | |
goto oomem; | |
__freedtoa(dtoaresult); | |
goto fp_common; | |
case 'e': | |
case 'E': | |
expchar = ch; | |
if (prec < 0) | |
prec = DEFPREC; | |
goto fp_begin; | |
case 'f': | |
case 'F': | |
expchar = '\0'; | |
goto fp_begin; | |
case 'g': | |
case 'G': | |
expchar = ch - ('g' - 'e'); | |
if (prec == 0) | |
prec = 1; | |
fp_begin: | |
if (prec < 0) | |
prec = DEFPREC; | |
if (flags & LONGDBL) { | |
fparg.ldbl = GETARG(long double); | |
dtoaresult = | |
__ldtoa(&fparg.ldbl, expchar ? 2 : 3, prec, | |
&expt, &signflag, &dtoaend); | |
} else { | |
fparg.dbl = GETARG(double); | |
dtoaresult = | |
__dtoa(fparg.dbl, expchar ? 2 : 3, prec, | |
&expt, &signflag, &dtoaend); | |
if (expt == 9999) | |
expt = INT_MAX; | |
} | |
if (dtoaresult == NULL) | |
goto oomem; | |
ndig = dtoaend - dtoaresult; | |
if (convbuf != NULL) | |
free(convbuf); | |
#ifndef NARROW | |
result = convbuf = __mbsconv(dtoaresult, -1); | |
#else | |
/*XXX inefficient*/ | |
result = convbuf = strdup(dtoaresult); | |
#endif | |
if (result == NULL) | |
goto oomem; | |
__freedtoa(dtoaresult); | |
fp_common: | |
if (signflag) | |
sign = '-'; | |
if (expt == INT_MAX) { /* inf or nan */ | |
if (*result == 'N') { | |
result = (ch >= 'a') ? STRCONST("nan") : | |
STRCONST("NAN"); | |
sign = '\0'; | |
} else | |
result = (ch >= 'a') ? STRCONST("inf") : | |
STRCONST("INF"); | |
size = 3; | |
break; | |
} | |
#else | |
//case 'e': | |
//case 'E': | |
//case 'f': | |
//case 'F': | |
//case 'g': | |
//case 'G': | |
// if (prec == -1) { | |
// prec = DEFPREC; | |
// } else if ((ch == 'g' || ch == 'G') && prec == 0) { | |
// prec = 1; | |
// } | |
case 'e': | |
case 'E': | |
expchar = ch; | |
if (prec < 0) | |
prec = DEFPREC; | |
goto fp_begin; | |
case 'f': | |
case 'F': | |
expchar = '\0'; | |
goto fp_begin; | |
case 'g': | |
case 'G': | |
expchar = ch - ('g' - 'e'); | |
if (prec == 0) | |
prec = 1; | |
fp_begin: | |
if (prec < 0) | |
prec = DEFPREC; | |
if (flags & LONGDBL) { | |
_double = (double) GETARG(long double); | |
} else { | |
_double = GETARG(double); | |
} | |
/* do this before tricky precision changes */ | |
if (isinf(_double)) { | |
if (_double < 0) | |
sign = '-'; | |
if (ch == 'E' || ch == 'F' || ch == 'G') | |
result = STRCONST("INF"); | |
else | |
result = STRCONST("inf"); | |
size = 3; | |
break; | |
} | |
if (isnan(_double)) { | |
if (ch == 'E' || ch == 'F' || ch == 'G') | |
result = STRCONST("NAN"); | |
else | |
result = STRCONST("nan"); | |
size = 3; | |
break; | |
} | |
flags |= FPT; | |
dtoaresult = cvt(_double, prec, flags, &softsign, &expt, ch, &ndig); | |
if (dtoaresult == NULL) | |
goto oomem; | |
if (convbuf != NULL) | |
free(convbuf); | |
#ifndef NARROW | |
result = convbuf = __mbsconv(dtoaresult, -1); | |
#else | |
/*XXX inefficient*/ | |
result = convbuf = strdup(dtoaresult); | |
#endif | |
if (result == NULL) | |
goto oomem; | |
__freedtoa(dtoaresult); | |
if (softsign) | |
sign = '-'; | |
#endif | |
flags |= FPT; | |
if (ch == 'g' || ch == 'G') { | |
if (expt > -4 && expt <= prec) { | |
/* Make %[gG] smell like %[fF] */ | |
expchar = '\0'; | |
if (flags & ALT) | |
prec -= expt; | |
else | |
prec = ndig - expt; | |
if (prec < 0) | |
prec = 0; | |
} else { | |
/* | |
* Make %[gG] smell like %[eE], but | |
* trim trailing zeroes if no # flag. | |
* | |
* Note: The precision field used with [gG] is the number significant | |
* digits to print. When converting to [eE] the digit before the | |
* decimal must not be included in the precision value. | |
*/ | |
if (!(flags & ALT)) | |
prec = ndig - 1; | |
} | |
} | |
if (expchar) { | |
dprec = prec; /* In some cases dprec will not be set. Make sure it is set now */ | |
expsize = exponent(expstr, expt - 1, expchar); | |
size = expsize + prec + 1; /* Leading digit + exponent string + precision */ | |
if (prec >= 1 || flags & ALT) | |
++size; /* Decimal point is added to character count */ | |
} else { | |
/* space for digits before decimal point */ | |
if (expt > 0) | |
size = expt; | |
else /* "0" */ | |
size = 1; | |
/* space for decimal pt and following digits */ | |
if (prec || flags & ALT) | |
size += prec + 1; | |
if (grouping && expt > 0) { | |
/* space for thousands' grouping */ | |
nseps = nrepeats = 0; | |
lead = expt; | |
while (*grouping != CHAR_MAX) { | |
if (lead <= *grouping) | |
break; | |
lead -= *grouping; | |
if (*(grouping+1)) { | |
nseps++; | |
grouping++; | |
} else | |
nrepeats++; | |
} | |
size += nseps + nrepeats; | |
} else | |
lead = expt; | |
} | |
break; | |
#endif /* !NO_FLOATING_POINT */ | |
case 'n': | |
/* | |
* Assignment-like behavior is specified if the | |
* value overflows or is otherwise unrepresentable. | |
* C99 says to use `signed char' for %hhn conversions. | |
*/ | |
if (flags & LLONGINT) | |
*GETARG(long long *) = ret; | |
else if (flags & SIZET) | |
*GETARG(ssize_t *) = (ssize_t)ret; | |
else if (flags & PTRDIFFT) | |
*GETARG(ptrdiff_t *) = ret; | |
else if (flags & INTMAXT) | |
*GETARG(intmax_t *) = ret; | |
else if (flags & LONGINT) | |
*GETARG(long *) = ret; | |
else if (flags & SHORTINT) | |
*GETARG(short *) = ret; | |
else if (flags & CHARINT) | |
*GETARG(signed char *) = ret; | |
else | |
*GETARG(int *) = ret; | |
continue; /* no output */ | |
case 'O': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'o': | |
if (flags & INTMAX_SIZE) | |
ujval = UJARG(); | |
else | |
ulval = UARG(); | |
base = 8; | |
goto nosign; | |
case 'p': | |
/*- | |
* ``The argument shall be a pointer to void. The | |
* value of the pointer is converted to a sequence | |
* of printable characters, in an implementation- | |
* defined manner.'' | |
* -- ANSI X3J11 | |
*/ | |
ujval = (uintmax_t) (UINTN) GETARG(void *); | |
base = 16; | |
xdigs = xdigs_lower; | |
flags = flags | INTMAXT; | |
ox[1] = 'x'; | |
goto nosign; | |
case 'S': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 's': | |
if (((flags & LONGINT) ? 1:0) != MULTI) { | |
if ((result = GETARG(CHAR_T *)) == NULL) | |
result = STRCONST("(null)"); | |
} else { | |
MCHAR_T *mc; | |
if (convbuf != NULL) | |
free(convbuf); | |
if ((mc = GETARG(MCHAR_T *)) == NULL) | |
result = STRCONST("(null)"); | |
else { | |
convbuf = SCONV(mc, prec); | |
if (convbuf == NULL) { | |
fp->_flags |= __SERR; | |
goto error; | |
} | |
result = convbuf; | |
} | |
} | |
if (prec >= 0) { | |
/* | |
* can't use STRLEN; can only look for the | |
* NUL in the first `prec' characters, and | |
* STRLEN() will go further. | |
*/ | |
CHAR_T *p = MEMCHR(result, 0, (size_t)prec); | |
if (p != NULL) { | |
size = p - result; | |
if (size > prec) | |
size = prec; | |
} else | |
size = prec; | |
} else | |
size = (int)STRLEN(result); | |
sign = '\0'; | |
break; | |
case 'U': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'u': | |
if (flags & INTMAX_SIZE) | |
ujval = UJARG(); | |
else | |
ulval = UARG(); | |
base = 10; | |
goto nosign; | |
case 'X': | |
xdigs = xdigs_upper; | |
goto hex; | |
case 'x': | |
xdigs = xdigs_lower; | |
hex: | |
if (flags & INTMAX_SIZE) | |
ujval = UJARG(); | |
else | |
ulval = UARG(); | |
base = 16; | |
/* leading 0x/X only if non-zero */ | |
if (flags & ALT && | |
(flags & INTMAX_SIZE ? ujval != 0 : ulval != 0)) | |
ox[1] = ch; | |
flags &= ~GROUPING; | |
/* unsigned conversions */ | |
nosign: sign = '\0'; | |
/*- | |
* ``... diouXx conversions ... if a precision is | |
* specified, the 0 flag will be ignored.'' | |
* -- ANSI X3J11 | |
*/ | |
number: if ((dprec = prec) >= 0) | |
flags &= ~ZEROPAD; | |
/*- | |
* ``The result of converting a zero value with an | |
* explicit precision of zero is no characters.'' | |
* -- ANSI X3J11 | |
* | |
* ``The C Standard is clear enough as is. The call | |
* printf("%#.0o", 0) should print 0.'' | |
* -- Defect Report #151 | |
*/ | |
result = cp = buf + BUF; | |
if (flags & INTMAX_SIZE) { | |
if (ujval != 0 || prec != 0 || | |
(flags & ALT && base == 8)) | |
{ | |
result = __ujtoa(ujval, cp, base, | |
flags & ALT, xdigs, | |
flags & GROUPING, thousands_sep, | |
grouping); | |
} | |
} else { | |
if (ulval != 0 || prec != 0 || | |
(flags & ALT && base == 8)) | |
result = __ultoa(ulval, cp, base, | |
flags & ALT, xdigs, | |
flags & GROUPING, thousands_sep, | |
grouping); | |
} | |
size = buf + BUF - result; | |
if (size > BUF) /* should never happen */ | |
abort(); | |
break; | |
default: /* "%?" prints ?, unless ? is NUL */ | |
if (ch == '\0') | |
goto done; | |
/* pretend it was %c with argument ch */ | |
*buf = ch; | |
result = buf; | |
size = 1; | |
sign = '\0'; | |
break; | |
} | |
/* | |
* All reasonable formats wind up here. At this point, `result' | |
* points to a string which (if not flags&LADJUST) should be | |
* padded out to `width' places. If flags&ZEROPAD, it should | |
* first be prefixed by any sign or other prefix; otherwise, | |
* it should be blank padded before the prefix is emitted. | |
* After any left-hand padding and prefixing, emit zeroes | |
* required by a decimal [diouxX] precision, then print the | |
* string proper, then emit zeroes required by any leftover | |
* floating precision; finally, if LADJUST, pad with blanks. | |
* | |
* Compute actual size, so we know how much to pad. | |
* size excludes decimal prec; realsz includes it. | |
*/ | |
realsz = dprec > size ? dprec : size; | |
if (sign) | |
realsz++; | |
if (ox[1]) | |
realsz += 2; | |
prsize = width > realsz ? width : realsz; | |
if ((unsigned)ret + prsize > INT_MAX) { | |
ret = END_OF_FILE; | |
goto error; | |
} | |
/* right-adjusting blank padding */ | |
if ((flags & (LADJUST|ZEROPAD)) == 0) | |
PAD(width - realsz, blanks); | |
/* prefix */ | |
if (sign) | |
PRINT(&sign, 1); | |
if (ox[1]) { /* ox[1] is either x, X, or \0 */ | |
ox[0] = '0'; | |
PRINT(ox, 2); | |
} | |
/* right-adjusting zero padding */ | |
if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) | |
PAD(width - realsz, zeroes); | |
/* leading zeroes from decimal precision */ | |
PAD(dprec - size, zeroes); | |
/* the string or number proper */ | |
#ifndef NO_FLOATING_POINT | |
if ((flags & FPT) == 0) { | |
PRINT(result, size); | |
} else { /* glue together f_p fragments */ | |
if (!expchar) { /* %[fF] or sufficiently short %[gG] */ | |
if (expt <= 0) { | |
PRINT(zeroes, 1); | |
if (prec || flags & ALT) | |
PRINT(decimal_point, 1); | |
PAD(-expt, zeroes); | |
/* already handled initial 0's */ | |
prec += expt; | |
} else { | |
PRINTANDPAD(result, convbuf + ndig, | |
lead, zeroes); | |
result += lead; | |
if (grouping) { | |
while (nseps>0 || nrepeats>0) { | |
if (nrepeats > 0) | |
nrepeats--; | |
else { | |
grouping--; | |
nseps--; | |
} | |
PRINT(&thousands_sep, | |
1); | |
PRINTANDPAD(result, | |
convbuf + ndig, | |
*grouping, zeroes); | |
result += *grouping; | |
} | |
if (result > convbuf + ndig) | |
result = convbuf + ndig; | |
} | |
if (prec || flags & ALT) { | |
buf[0] = *decimal_point; | |
PRINT(buf, 1); | |
} | |
} | |
PRINTANDPAD(result, convbuf + ndig, prec, | |
zeroes); | |
} else { /* %[eE] or sufficiently long %[gG] */ | |
if (prec >= 1 || flags & ALT) { | |
buf[0] = *result++; | |
buf[1] = *decimal_point; | |
PRINT(buf, 2); | |
PRINT(result, ndig-1); | |
PAD(prec - ndig, zeroes); | |
} else /* XeYYY */ | |
PRINT(result, 1); | |
PRINT(expstr, expsize); | |
} | |
} | |
#else | |
PRINT(result, size); | |
#endif | |
/* left-adjusting padding (always blank) */ | |
if (flags & LADJUST) | |
PAD(width - realsz, blanks); | |
/* finally, adjust ret */ | |
ret += prsize; | |
FLUSH(); | |
} | |
done: | |
FLUSH(); | |
error: | |
va_end(orgap); | |
if (convbuf != NULL) | |
free(convbuf); | |
if (__sferror(fp)) | |
ret = END_OF_FILE; | |
if ((argtable != NULL) && (argtable != statargtable)) | |
free (argtable); | |
return (ret); | |
/* NOTREACHED */ | |
oomem: | |
errno = ENOMEM; | |
ret = END_OF_FILE; | |
goto error; | |
} | |
/* | |
* Find all arguments when a positional parameter is encountered. Returns a | |
* table, indexed by argument number, of pointers to each arguments. The | |
* initial argument table should be an array of STATIC_ARG_TBL_SIZE entries. | |
* It will be replaces with a malloc-ed one if it overflows. | |
*/ | |
static int | |
__find_arguments(const CHAR_T *fmt0, va_list ap, union arg **argtable) | |
{ | |
CHAR_T *fmt; /* format string */ | |
int ch; /* character from fmt */ | |
int n, n2; /* handy integer (short term usage) */ | |
CHAR_T *cp; /* handy char pointer (short term usage) */ | |
int flags; /* flags as above */ | |
enum typeid *typetable; /* table of types */ | |
enum typeid stattypetable [STATIC_ARG_TBL_SIZE]; | |
int tablesize; /* current size of type table */ | |
int tablemax; /* largest used index in table */ | |
int nextarg; /* 1-based argument index */ | |
/* | |
* Add an argument type to the table, expanding if necessary. | |
*/ | |
#define ADDTYPE(type) \ | |
do { \ | |
if (nextarg >= tablesize) \ | |
if (__grow_type_table(nextarg, &typetable, \ | |
&tablesize) == -1) \ | |
return -1; \ | |
if (nextarg > tablemax) \ | |
tablemax = nextarg; \ | |
typetable[nextarg++] = type; \ | |
} while (/*CONSTCOND*/0) | |
#define ADDSARG() \ | |
do { \ | |
if (flags & INTMAXT) \ | |
ADDTYPE(T_INTMAXT); \ | |
else if (flags & SIZET) \ | |
ADDTYPE(T_SIZET); \ | |
else if (flags & PTRDIFFT) \ | |
ADDTYPE(T_PTRDIFFT); \ | |
else if (flags & LLONGINT) \ | |
ADDTYPE(T_LLONG); \ | |
else if (flags & LONGINT) \ | |
ADDTYPE(T_LONG); \ | |
else \ | |
ADDTYPE(T_INT); \ | |
} while (/*CONSTCOND*/0) | |
#define ADDUARG() \ | |
do { \ | |
if (flags & INTMAXT) \ | |
ADDTYPE(T_UINTMAXT); \ | |
else if (flags & SIZET) \ | |
ADDTYPE(T_SIZET); \ | |
else if (flags & PTRDIFFT) \ | |
ADDTYPE(T_PTRDIFFT); \ | |
else if (flags & LLONGINT) \ | |
ADDTYPE(T_U_LLONG); \ | |
else if (flags & LONGINT) \ | |
ADDTYPE(T_U_LONG); \ | |
else \ | |
ADDTYPE(T_U_INT); \ | |
} while (/*CONSTCOND*/0) | |
/* | |
* Add * arguments to the type array. | |
*/ | |
#define ADDASTER() \ | |
n2 = 0; \ | |
cp = fmt; \ | |
while (is_digit(*cp)) { \ | |
n2 = 10 * n2 + to_digit(*cp); \ | |
cp++; \ | |
} \ | |
if (*cp == '$') { \ | |
int hold = nextarg; \ | |
nextarg = n2; \ | |
ADDTYPE(T_INT); \ | |
nextarg = hold; \ | |
fmt = ++cp; \ | |
} else { \ | |
ADDTYPE(T_INT); \ | |
} | |
fmt = (CHAR_T *)__UNCONST(fmt0); | |
typetable = stattypetable; | |
tablesize = STATIC_ARG_TBL_SIZE; | |
tablemax = 0; | |
nextarg = 1; | |
for (n = 0; n < STATIC_ARG_TBL_SIZE; n++) | |
typetable[n] = T_UNUSED; | |
/* | |
* Scan the format for conversions (`%' character). | |
*/ | |
for (;;) { | |
for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) | |
/* void */; | |
if (ch == '\0') | |
goto done; | |
fmt++; /* skip over '%' */ | |
flags = 0; | |
rflag: ch = *fmt++; | |
reswitch: switch (ch) { | |
case ' ': | |
case '#': | |
goto rflag; | |
case '*': | |
ADDASTER (); | |
goto rflag; | |
case '-': | |
case '+': | |
case '\'': | |
goto rflag; | |
case '.': | |
if ((ch = *fmt++) == '*') { | |
ADDASTER (); | |
goto rflag; | |
} | |
while (is_digit(ch)) { | |
ch = *fmt++; | |
} | |
goto reswitch; | |
case '0': | |
goto rflag; | |
case '1': case '2': case '3': case '4': | |
case '5': case '6': case '7': case '8': case '9': | |
n = 0; | |
do { | |
n = 10 * n + to_digit(ch); | |
ch = *fmt++; | |
} while (is_digit(ch)); | |
if (ch == '$') { | |
nextarg = n; | |
goto rflag; | |
} | |
goto reswitch; | |
#ifndef NO_FLOATING_POINT | |
case 'L': | |
flags |= LONGDBL; | |
goto rflag; | |
#endif | |
case 'h': | |
if (flags & SHORTINT) { | |
flags &= ~SHORTINT; | |
flags |= CHARINT; | |
} else | |
flags |= SHORTINT; | |
goto rflag; | |
case 'j': | |
flags |= INTMAXT; | |
goto rflag; | |
case 'l': | |
if (flags & LONGINT) { | |
flags &= ~LONGINT; | |
flags |= LLONGINT; | |
} else | |
flags |= LONGINT; | |
goto rflag; | |
case 'q': | |
flags |= LLONGINT; /* not necessarily */ | |
goto rflag; | |
case 't': | |
flags |= PTRDIFFT; | |
goto rflag; | |
case 'z': | |
flags |= SIZET; | |
goto rflag; | |
case 'C': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'c': | |
if (flags & LONGINT) | |
ADDTYPE(T_WINT); | |
else | |
ADDTYPE(T_INT); | |
break; | |
case 'D': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'd': | |
case 'i': | |
ADDSARG(); | |
break; | |
#ifndef NO_FLOATING_POINT | |
case 'a': | |
case 'A': | |
case 'e': | |
case 'E': | |
case 'f': | |
case 'g': | |
case 'G': | |
if (flags & LONGDBL) | |
ADDTYPE(T_LONG_DOUBLE); | |
else | |
ADDTYPE(T_DOUBLE); | |
break; | |
#endif /* !NO_FLOATING_POINT */ | |
case 'n': | |
if (flags & INTMAXT) | |
ADDTYPE(TP_INTMAXT); | |
else if (flags & PTRDIFFT) | |
ADDTYPE(TP_PTRDIFFT); | |
else if (flags & SIZET) | |
ADDTYPE(TP_SIZET); | |
else if (flags & LLONGINT) | |
ADDTYPE(TP_LLONG); | |
else if (flags & LONGINT) | |
ADDTYPE(TP_LONG); | |
else if (flags & SHORTINT) | |
ADDTYPE(TP_SHORT); | |
else if (flags & CHARINT) | |
ADDTYPE(TP_SCHAR); | |
else | |
ADDTYPE(TP_INT); | |
continue; /* no output */ | |
case 'O': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'o': | |
ADDUARG(); | |
break; | |
case 'p': | |
ADDTYPE(TP_VOID); | |
break; | |
case 'S': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 's': | |
if (flags & LONGINT) | |
ADDTYPE(TP_WCHAR); | |
else | |
ADDTYPE(TP_CHAR); | |
break; | |
case 'U': | |
flags |= LONGINT; | |
/*FALLTHROUGH*/ | |
case 'u': | |
case 'X': | |
case 'x': | |
ADDUARG(); | |
break; | |
default: /* "%?" prints ?, unless ? is NUL */ | |
if (ch == '\0') | |
goto done; | |
break; | |
} | |
} | |
done: | |
/* | |
* Build the argument table. | |
*/ | |
if (tablemax >= STATIC_ARG_TBL_SIZE) { | |
*argtable = (union arg *) | |
malloc (sizeof (union arg) * (tablemax + 1)); | |
if (*argtable == NULL) | |
return -1; | |
} | |
(*argtable) [0].intarg = 0; | |
for (n = 1; n <= tablemax; n++) { | |
switch (typetable [n]) { | |
case T_UNUSED: /* whoops! */ | |
(*argtable) [n].intarg = va_arg (ap, int); | |
break; | |
case TP_SCHAR: | |
(*argtable) [n].pschararg = va_arg (ap, signed char *); | |
break; | |
case TP_SHORT: | |
(*argtable) [n].pshortarg = va_arg (ap, short *); | |
break; | |
case T_INT: | |
(*argtable) [n].intarg = va_arg (ap, int); | |
break; | |
case T_U_INT: | |
(*argtable) [n].uintarg = va_arg (ap, unsigned int); | |
break; | |
case TP_INT: | |
(*argtable) [n].pintarg = va_arg (ap, int *); | |
break; | |
case T_LONG: | |
(*argtable) [n].longarg = va_arg (ap, long); | |
break; | |
case T_U_LONG: | |
(*argtable) [n].ulongarg = va_arg (ap, unsigned long); | |
break; | |
case TP_LONG: | |
(*argtable) [n].plongarg = va_arg (ap, long *); | |
break; | |
case T_LLONG: | |
(*argtable) [n].longlongarg = va_arg (ap, long long); | |
break; | |
case T_U_LLONG: | |
(*argtable) [n].ulonglongarg = va_arg (ap, unsigned long long); | |
break; | |
case TP_LLONG: | |
(*argtable) [n].plonglongarg = va_arg (ap, long long *); | |
break; | |
case T_PTRDIFFT: | |
(*argtable) [n].ptrdiffarg = va_arg (ap, ptrdiff_t); | |
break; | |
case TP_PTRDIFFT: | |
(*argtable) [n].pptrdiffarg = va_arg (ap, ptrdiff_t *); | |
break; | |
case T_SIZET: | |
(*argtable) [n].sizearg = va_arg (ap, size_t); | |
break; | |
case TP_SIZET: | |
(*argtable) [n].psizearg = va_arg (ap, size_t *); | |
break; | |
case T_INTMAXT: | |
(*argtable) [n].intmaxarg = va_arg (ap, intmax_t); | |
break; | |
case T_UINTMAXT: | |
(*argtable) [n].uintmaxarg = va_arg (ap, uintmax_t); | |
break; | |
case TP_INTMAXT: | |
(*argtable) [n].pintmaxarg = va_arg (ap, intmax_t *); | |
break; | |
case T_DOUBLE: | |
#ifndef NO_FLOATING_POINT | |
(*argtable) [n].doublearg = va_arg (ap, double); | |
#endif | |
break; | |
case T_LONG_DOUBLE: | |
#ifndef NO_FLOATING_POINT | |
(*argtable) [n].longdoublearg = va_arg (ap, long double); | |
#endif | |
break; | |
case TP_CHAR: | |
(*argtable) [n].pchararg = va_arg (ap, char *); | |
break; | |
case TP_VOID: | |
(*argtable) [n].pvoidarg = va_arg (ap, void *); | |
break; | |
case T_WINT: | |
(*argtable) [n].wintarg = va_arg (ap, int); | |
break; | |
case TP_WCHAR: | |
(*argtable) [n].pwchararg = va_arg (ap, wchar_t *); | |
break; | |
} | |
} | |
if ((typetable != NULL) && (typetable != stattypetable)) | |
free (typetable); | |
return 0; | |
} | |
/* | |
* Increase the size of the type table. | |
*/ | |
static int | |
__grow_type_table (int nextarg, enum typeid **typetable, int *tablesize) | |
{ | |
enum typeid *const oldtable = *typetable; | |
const int oldsize = *tablesize; | |
enum typeid *newtable; | |
int n, newsize = oldsize * 2; | |
if (newsize < nextarg + 1) | |
newsize = nextarg + 1; | |
if (oldsize == STATIC_ARG_TBL_SIZE) { | |
if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL) | |
return -1; | |
memcpy(newtable, oldtable, oldsize * sizeof(enum typeid)); | |
} else { | |
newtable = realloc(oldtable, newsize * sizeof(enum typeid)); | |
if (newtable == NULL) { | |
free(oldtable); | |
return -1; | |
} | |
} | |
for (n = oldsize; n < newsize; n++) | |
newtable[n] = T_UNUSED; | |
*typetable = newtable; | |
*tablesize = newsize; | |
return 0; | |
} | |
#ifndef NO_FLOATING_POINT | |
#ifndef WIDE_DOUBLE | |
static char * | |
cvt(double value, int ndigits, int flags, char *sign, int *decpt, int ch, | |
int *length) | |
{ | |
int mode, dsgn; | |
char *digits, *bp, *rve; | |
_DIAGASSERT(decpt != NULL); | |
_DIAGASSERT(length != NULL); | |
_DIAGASSERT(sign != NULL); | |
if (ch == 'f') { | |
mode = 3; /* ndigits after the decimal point */ | |
} else { | |
/* To obtain ndigits after the decimal point for the 'e' | |
* and 'E' formats, round to ndigits + 1 significant | |
* figures. | |
*/ | |
if (ch == 'e' || ch == 'E') { | |
ndigits++; | |
} | |
mode = 2; /* ndigits significant digits */ | |
} | |
digits = __dtoa(value, mode, ndigits, decpt, &dsgn, &rve); | |
if (digits == NULL) | |
return NULL; | |
if (dsgn) { | |
value = -value; | |
*sign = '-'; | |
} else | |
*sign = '\000'; | |
if ((ch != 'g' && ch != 'G') || flags & ALT) { /* Print trailing zeros */ | |
bp = digits + ndigits; | |
if (ch == 'f') { | |
if (*digits == '0' && value) | |
*decpt = -ndigits + 1; | |
bp += *decpt; | |
} | |
while (rve < bp) | |
*rve++ = '0'; | |
} | |
*length = rve - digits; | |
return digits; | |
} | |
#endif | |
static int | |
exponent(CHAR_T *p0, int expo, int fmtch) | |
{ | |
CHAR_T *p, *t; | |
CHAR_T expbuf[MAXEXPDIG]; | |
p = p0; | |
*p++ = fmtch; | |
if (expo < 0) { | |
expo = -expo; | |
*p++ = '-'; | |
} | |
else | |
*p++ = '+'; | |
t = expbuf + MAXEXPDIG; | |
if (expo > 9) { | |
do { | |
*--t = to_char(expo % 10); | |
} while ((expo /= 10) > 9); | |
*--t = to_char(expo); | |
for (; t < expbuf + MAXEXPDIG; *p++ = *t++); | |
} | |
else { | |
/* | |
* Exponents for decimal floating point conversions | |
* (%[eEgG]) must be at least two characters long, | |
* whereas exponents for hexadecimal conversions can | |
* be only one character long. | |
*/ | |
if (fmtch == 'e' || fmtch == 'E') | |
*p++ = '0'; | |
*p++ = to_char(expo); | |
} | |
return (p - p0); | |
} | |
#endif /* !NO_FLOATING_POINT */ |