/* $Id: suio_vuprintf.C,v 1.5 2005/07/18 21:23:20 dm Exp $ */
/*-
* Copyright (c) 1990 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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 REGENTS 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 REGENTS 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.
*/
/*
*
* Copyright (C) 1998 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#undef FLOATING_POINT
#include "suio++.h"
#ifdef FLOATING_POINT
#include <locale.h>
#include <math.h>
/* 11-bit exponent (VAX G floating point) is 308 decimal digits */
#define MAXEXP 308
/* 128 bit fraction takes up 39 decimal digits; max reasonable precision */
#define MAXFRACT 39
#define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
#define DEFPREC 6
static char *cvt (double, int, int, char *, int *, int, int *);
static int exponent (char *, int, int);
#else /* ! FLOATING_POINT */
#define BUF 40
#endif /* ! FLOATING_POINT */
/*
* 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) ((n) + '0')
/*
* Flags used during conversion.
*/
#define ALT 0x001 /* alternate form */
#define HEXPREFIX 0x002 /* add 0x or 0X prefix */
#define LADJUST 0x004 /* left adjustment */
#define LONGDBL 0x008 /* long double; unimplemented */
#define LONGINT 0x010 /* long integer */
#define QUADINT 0x020 /* quad integer */
#define SHORTINT 0x040 /* short integer */
#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */
#define FPT 0x100 /* Floating point number */
#define NOCOPY 0x200 /* String contents doesn't have to be copied */
void
#ifndef DSPRINTF_DEBUG
suio_vuprintf (struct suio *uio, const char *_fmt, va_list ap)
#else /* DSPRINTF_DEBUG */
__suio_vuprintf (const char *line, struct suio *uio,
const char *_fmt, va_list ap)
#endif /* DSPRINTF_DEBUG */
{
char *fmt = (char *) _fmt;
register int ch; /* character from fmt */
register int n, m; /* handy integers (short term usage) */
register char *cp; /* handy char pointer (short term usage) */
register int flags; /* flags as above */
int width; /* width from format (%8d), or 0 */
int prec; /* precision from format (%.3d), or -1 */
char sign; /* sign prefix (' ', '+', '-', or \0) */
#ifdef FLOATING_POINT
char *decimal_point = ".";
char softsign; /* temporary negative sign for floats */
double _double = 0.0; /* double precision arguments %[eEfgG] */
int expt; /* integer value of exponent */
int expsize = 0; /* character count for expstr */
int ndig; /* actual number of digits returned by cvt */
char expstr[7]; /* buffer for exponent string */
#endif
#define quad_t int64_t
#define u_quad_t u_int64_t
u_quad_t _uquad; /* integer arguments %[diouxX] */
enum {
OCT, DEC, HEX
} base; /* base for [diouxX] conversion */
int dprec; /* a copy of prec if [diouxX], 0 otherwise */
int realsz; /* field size expanded by dprec */
int size; /* size of converted field or string */
char *xdigs = ""; /* digits for [xX] conversion */
char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */
char ox[2]; /* space for 0x hex-prefix */
/*
* To extend shorts properly, we need both signed and unsigned
* argument extraction methods.
*/
#define SARG() \
(flags&QUADINT ? va_arg(ap, quad_t) : \
flags&LONGINT ? va_arg(ap, long) : \
flags&SHORTINT ? (long)(short)va_arg(ap, int) : \
(long)va_arg(ap, int))
#define UARG() \
(flags&QUADINT ? va_arg(ap, u_quad_t) : \
flags&LONGINT ? va_arg(ap, u_long) : \
flags&SHORTINT ? (u_long)(u_short)va_arg(ap, int) : \
(u_long)va_arg(ap, u_int))
/*
* Scan the format for conversions (`%' character).
*/
for (;;) {
cp = fmt;
while (*fmt > 0) {
if (*fmt == '%')
break;
fmt++;
}
if ((m = fmt - cp) != 0)
suio_print (uio, cp, m);
if (!*fmt)
goto done;
fmt++; /* skip over '%' */
flags = 0;
dprec = 0;
width = 0;
prec = -1;
sign = '\0';
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.
*/
if ((width = va_arg (ap, int)) >= 0)
goto rflag;
width = -width;
/* FALLTHROUGH */
case '-':
flags |= LADJUST;
goto rflag;
case '+':
sign = '+';
goto rflag;
case '.':
if ((ch = *fmt++) == '*') {
n = va_arg (ap, int);
prec = n < 0 ? -1 : n;
goto rflag;
}
n = 0;
while (is_digit (ch)) {
n = 10 * n + to_digit (ch);
ch = *fmt++;
}
prec = n < 0 ? -1 : n;
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));
width = n;
goto reswitch;
#ifdef FLOATING_POINT
case 'L':
flags |= LONGDBL;
goto rflag;
#endif
case 'h':
flags |= SHORTINT;
goto rflag;
case 'l':
if (*fmt == 'l') {
fmt++;
flags |= QUADINT;
}
else {
flags |= LONGINT;
}
goto rflag;
case 'q':
flags |= QUADINT;
goto rflag;
case 'c':
*(cp = buf) = va_arg (ap, int);
size = 1;
sign = '\0';
break;
case 'D':
flags |= LONGINT;
/*FALLTHROUGH */
case 'd':
case 'i':
_uquad = SARG ();
if ((quad_t) _uquad < 0) {
_uquad = -_uquad;
sign = '-';
}
base = DEC;
goto number;
#ifdef FLOATING_POINT
case 'e':
case 'E':
case 'f':
case 'g':
case 'G':
if (prec == -1) {
prec = DEFPREC;
}
else if ((ch == 'g' || ch == 'G') && prec == 0) {
prec = 1;
}
if (flags & LONGDBL) {
_double = (double) va_arg (ap, long double);
}
else {
_double = va_arg (ap, double);
}
/* do this before tricky precision changes */
if (isinf (_double)) {
if (_double < 0)
sign = '-';
cp = "Inf";
size = 3;
break;
}
if (isnan (_double)) {
cp = "NaN";
size = 3;
break;
}
flags |= FPT;
cp = cvt (_double, prec, flags, &softsign,
&expt, ch, &ndig);
if (ch == 'g' || ch == 'G') {
if (expt <= -4 || expt > prec)
ch = (ch == 'g') ? 'e' : 'E';
else
ch = 'g';
}
if (ch <= 'e') { /* 'e' or 'E' fmt */
--expt;
expsize = exponent (expstr, expt, ch);
size = expsize + ndig;
if (ndig > 1 || flags & ALT)
++size;
}
else if (ch == 'f') { /* f fmt */
if (expt > 0) {
size = expt;
if (prec || flags & ALT)
size += prec + 1;
}
else /* "0.X" */
size = prec + 2;
}
else if (expt >= ndig) { /* fixed g fmt */
size = expt;
if (flags & ALT)
++size;
}
else
size = ndig + (expt > 0 ?
1 : 2 - expt);
if (softsign)
sign = '-';
break;
#endif /* FLOATING_POINT */
#if 0
/* Disable "%n", as we don't need it--you can always print to a
* suio and use suio::resid () to get the buffer size. */
case 'n':
if (flags & QUADINT)
*va_arg (ap, quad_t *) = uio->resid ();
else if (flags & LONGINT)
*va_arg (ap, long *) = uio->resid ();
else if (flags & SHORTINT)
*va_arg (ap, short *) = uio->resid ();
else
*va_arg (ap, int *) = uio->resid ();
continue; /* no output */
#endif
case 'O':
flags |= LONGINT;
/*FALLTHROUGH */
case 'o':
_uquad = UARG ();
base = OCT;
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
*/
/* NOSTRICT */
_uquad = (u_long) va_arg (ap, void *);
base = HEX;
xdigs = "0123456789abcdef";
flags |= HEXPREFIX;
ch = 'x';
goto nosign;
case 'm':
cp = strerror (errno);
goto gotcp;
case 's':
cp = va_arg (ap, char *);
gotcp:
if (cp == NULL)
panic ("suio_vuprintf: NULL pointer\n");
//cp = "(null)";
if (prec >= 0) {
/*
* can't use strlen; can only look for the
* NUL in the first `prec' characters, and
* strlen() will go further.
*/
char *p = (char *) memchr (cp, 0, prec);
if (p != NULL) {
size = p - cp;
if (size > prec)
size = prec;
}
else
size = prec;
}
else
size = strlen (cp);
sign = '\0';
if (!(flags & ALT))
flags |= NOCOPY;
break;
case 'U':
flags |= LONGINT;
/*FALLTHROUGH */
case 'u':
_uquad = UARG ();
base = DEC;
goto nosign;
case 'X':
xdigs = "0123456789ABCDEF";
goto hex;
case 'x':
xdigs = "0123456789abcdef";
hex:
_uquad = UARG ();
base = HEX;
/* leading 0x/X only if non-zero */
if (flags & ALT && _uquad != 0)
flags |= HEXPREFIX;
/* 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
*/
cp = buf + BUF;
if (_uquad != 0 || prec != 0) {
/*
* Unsigned mod is hard, and unsigned mod
* by a constant is easier than that by
* a variable; hence this switch.
*/
switch (base) {
case OCT:
do {
*--cp = to_char (_uquad & 7);
_uquad >>= 3;
} while (_uquad);
/* handle octal leading 0 */
if (flags & ALT && *cp != '0')
*--cp = '0';
break;
case DEC:
/* many numbers are 1 digit */
while (_uquad >= 10) {
*--cp = to_char (_uquad % 10);
_uquad /= 10;
}
*--cp = to_char (_uquad);
break;
case HEX:
do {
*--cp = xdigs[_uquad & 15];
_uquad >>= 4;
} while (_uquad);
break;
default:
cp = "bug in vfprintf: bad base";
size = strlen (cp);
goto skipsize;
}
}
size = buf + BUF - cp;
skipsize:
break;
default: /* "%?" prints ?, unless ? is NUL */
if (ch == '\0')
goto done;
/* pretend it was %c with argument ch */
cp = buf;
*cp = ch;
size = 1;
sign = '\0';
break;
}
/*
* All reasonable formats wind up here. At this point, `cp'
* 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++;
else if (flags & HEXPREFIX)
realsz += 2;
/* right-adjusting blank padding */
if ((flags & (LADJUST | ZEROPAD)) == 0)
suio_fill (uio, ' ', width - realsz);
/* prefix */
if (sign) {
suio_copy (uio, &sign, 1);
}
else if (flags & HEXPREFIX) {
ox[0] = '0';
ox[1] = ch;
suio_copy (uio, ox, 2);
}
/* right-adjusting zero padding */
if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
suio_fill (uio, '0', width - realsz);
/* leading zeroes from decimal precision */
suio_fill (uio, '0', dprec - size);
/* the string or number proper */
#ifdef FLOATING_POINT
if ((flags & FPT) == 0) {
if (flags & NOCOPY)
__suio_printcheck (line, uio, cp, size);
else
suio_copy (uio, cp, size);
}
else { /* glue together f_p fragments */
if (ch >= 'f') { /* 'f' or 'g' */
if (_double == 0) {
/* kludge for __dtoa irregularity */
suio_print (uio, "0", 1);
if (expt < ndig || (flags & ALT) != 0) {
suio_print (uio, decimal_point, 1);
suio_fill (uio, '0', ndig - 1);
}
}
else if (expt <= 0) {
suio_print (uio, "0", 1);
suio_print (uio, decimal_point, 1);
suio_fill (uio, '0', -expt);
suio_copy (uio, cp, ndig);
}
else if (expt >= ndig) {
suio_copy (uio, cp, ndig);
suio_fill (uio, '0', expt - ndig);
if (flags & ALT)
suio_print (uio, ".", 1);
}
else {
suio_copy (uio, cp, expt);
cp += expt;
suio_print (uio, ".", 1);
suio_copy (uio, cp, ndig - expt);
}
}
else { /* 'e' or 'E' */
if (ndig > 1 || flags & ALT) {
ox[0] = *cp++;
ox[1] = '.';
suio_copy (uio, ox, 2);
if (_double || (flags & ALT) == 0) {
suio_copy (uio, cp, ndig - 1);
}
else /* 0.[0..] */
/* __dtoa irregularity */
suio_fill (uio, '0', ndig - 1);
}
else /* XeYYY */
suio_copy (uio, cp, 1);
suio_copy (uio, expstr, expsize);
}
}
#else
if (flags & NOCOPY)
#ifndef DSPRINTF_DEBUG
suio_print (uio, cp, size);
#else /* DSPRINTF_DEBUG */
__suio_printcheck (line, uio, cp, size);
#endif /* DSPRINTF_DEBUG */
else
suio_copy (uio, cp, size);
#endif
/* left-adjusting padding (always blank) */
if (flags & LADJUST)
suio_fill (uio, ' ', width - realsz);
}
done:
return;
}
#ifdef FLOATING_POINT
extern char *__dtoa (double, int, int, int *, int *, char **);
static char *
cvt (value, ndigits, flags, sign, decpt, ch, length)
double value;
int ndigits, flags, *decpt, ch, *length;
char *sign;
{
int mode, dsgn;
char *digits, *bp, *rve;
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 */
}
if (value < 0) {
value = -value;
*sign = '-';
}
else
*sign = '\000';
digits = __dtoa (value, mode, ndigits, decpt, &dsgn, &rve);
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;
}
if (value == 0) /* kludge for __dtoa irregularity */
rve = bp;
while (rve < bp)
*rve++ = '0';
}
*length = rve - digits;
return (digits);
}
static int
exponent (p0, exp, fmtch)
char *p0;
int exp, fmtch;
{
register char *p, *t;
char expbuf[MAXEXP];
p = p0;
*p++ = fmtch;
if (exp < 0) {
exp = -exp;
*p++ = '-';
}
else
*p++ = '+';
t = expbuf + MAXEXP;
if (exp > 9) {
do {
*--t = to_char (exp % 10);
} while ((exp /= 10) > 9);
*--t = to_char (exp);
for (; t < expbuf + MAXEXP; *p++ = *t++);
}
else {
*p++ = '0';
*p++ = to_char (exp);
}
return (p - p0);
}
#endif /* FLOATING_POINT */
void
#ifndef DSPRINTF_DEBUG
suio_uprintf (struct suio *uio, const char *fmt, ...)
#else /* DSPRINTF_DEBUG */
__suio_uprintf (const char *line, struct suio *uio, const char *fmt, ...)
#endif /* DSPRINTF_DEBUG */
{
va_list ap;
va_start (ap, fmt);
#ifndef DSPRINTF_DEBUG
suio_vuprintf (uio, fmt, ap);
#else /* DSPRINTF_DEBUG */
__suio_vuprintf (line, uio, fmt, ap);
#endif /* DSPRINTF_DEBUG */
va_end (ap);
}
syntax highlighted by Code2HTML, v. 0.9.1