Files
retrobsd/src/libc/stdio/doprnt.c
2014-04-12 01:25:31 +02:00

804 lines
16 KiB
C

/*
* Scaled down version of printf(3).
* Based on FreeBSD sources, heavily rewritten.
*
* Copyright (c) 1980 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
*/
/*
* Two additional formats:
*
* The format %b is supported to decode error registers.
* Its usage is:
*
* printf("reg=%b\n", regval, "<base><arg>*");
*
* where <base> is the output base expressed as a control character, e.g.
* \10 gives octal; \20 gives hex. Each arg is a sequence of characters,
* the first of which gives the bit number to be inspected (origin 1), and
* the next characters (up to a control character, i.e. a character <= 32),
* give the name of the register. Thus:
*
* kvprintf("reg=%b\n", 3, "\10\2BITTWO\1BITONE\n");
*
* would produce output:
*
* reg=3<BITTWO,BITONE>
*
* The format %D -- Hexdump, takes a pointer. Sharp flag - use `:' as
* a separator, instead of a space. For example:
*
* ("%6D", ptr) -> XX XX XX XX XX XX
* ("%#*D", len, ptr) -> XX:XX:XX:XX ...
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <float.h>
#include <math.h>
/* Max number conversion buffer length: a long in base 2, plus NUL byte. */
//#define MAXNBUF (sizeof(long) * 8 + 1)
/* Max number conversion buffer length: MAXEXP(308) + MAXFRACTION(15) + 2 */
#define MAXNBUF 308+15+2
static unsigned char *ksprintn (unsigned char *buf, unsigned long v, unsigned char base,
int width, unsigned char *lp);
static unsigned char mkhex (unsigned char ch);
static int cvt (double number, int prec, int sharpflag, unsigned char *negp,
unsigned char fmtch, unsigned char *startp, unsigned char *endp);
union {
unsigned long u32;
va_list ap32;
} x;
int
_doprnt (char const *fmt, va_list ap, FILE *stream)
{
#define PUTC(c) { putc (c, stream); ++retval; }
unsigned char nbuf [MAXNBUF], padding, *q;
const unsigned char *s;
unsigned char c, base, lflag, ladjust, sharpflag, neg, dot, size;
int n, width, dwidth, retval, uppercase, extrazeros, sign;
unsigned long ul;
if (! stream)
return 0;
if (! fmt)
fmt = "(null)\n";
retval = 0;
for (;;) {
while ((c = *fmt++) != '%') {
if (! c)
return retval;
PUTC (c);
}
padding = ' ';
width = 0; extrazeros = 0;
lflag = 0; ladjust = 0; sharpflag = 0; neg = 0;
sign = 0; dot = 0; uppercase = 0; dwidth = -1;
reswitch: switch (c = *fmt++) {
case '.':
dot = 1;
padding = ' ';
dwidth = 0;
goto reswitch;
case '#':
sharpflag = 1;
goto reswitch;
case '+':
sign = -1;
goto reswitch;
case '-':
ladjust = 1;
goto reswitch;
case '%':
PUTC (c);
break;
case '*':
if (! dot) {
width = va_arg (ap, int);
if (width < 0) {
ladjust = !ladjust;
width = -width;
}
} else {
dwidth = va_arg (ap, int);
}
goto reswitch;
case '0':
if (! dot) {
padding = '0';
goto reswitch;
}
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
for (n=0; ; ++fmt) {
n = n * 10 + c - '0';
c = *fmt;
if (c < '0' || c > '9')
break;
}
if (dot)
dwidth = n;
else
width = n;
goto reswitch;
case 'b':
ul = va_arg (ap, int);
s = va_arg (ap, const unsigned char*);
q = ksprintn (nbuf, ul, *s++, -1, 0);
while (*q)
PUTC (*q--);
if (! ul)
break;
size = 0;
while (*s) {
n = *s++;
if ((char) (ul >> (n-1)) & 1) {
PUTC (size ? ',' : '<');
for (; (n = *s) > ' '; ++s)
PUTC (n);
size = 1;
} else
while (*s > ' ')
++s;
}
if (size)
PUTC ('>');
break;
case 'c':
if (! ladjust && width > 0)
while (width--)
PUTC (' ');
PUTC (va_arg (ap, int));
if (ladjust && width > 0)
while (width--)
PUTC (' ');
break;
case 'D':
s = va_arg (ap, const unsigned char*);
if (! width)
width = 16;
if (sharpflag)
padding = ':';
while (width--) {
c = *s++;
PUTC (mkhex (c >> 4));
PUTC (mkhex (c));
if (width)
PUTC (padding);
}
break;
case 'd':
ul = lflag ? va_arg (ap, long) : va_arg (ap, int);
if (! sign) sign = 1;
base = 10;
goto number;
case 'l':
lflag = 1;
goto reswitch;
case 'o':
ul = lflag ? va_arg (ap, unsigned long) :
va_arg (ap, unsigned int);
base = 8;
goto nosign;
case 'p':
ul = (size_t) va_arg (ap, void*);
if (! ul) {
s = (const unsigned char*) "(nil)";
goto string;
}
base = 16;
sharpflag = (width == 0);
goto nosign;
case 'n':
ul = lflag ? va_arg (ap, unsigned long) :
sign ? (unsigned long) va_arg (ap, int) :
va_arg (ap, unsigned int);
base = 10;
goto number;
case 's':
s = va_arg (ap, unsigned char*);
if (! s)
s = (const unsigned char*) "(null)";
string: if (! dot)
n = strlen ((char*)s);
else
for (n=0; n<dwidth && s[n]; n++)
continue;
width -= n;
if (! ladjust && width > 0)
while (width--)
PUTC (' ');
while (n--)
PUTC (*s++);
if (ladjust && width > 0)
while (width--)
PUTC (' ');
break;
case 'r':
/* Saturated counters. */
base = 10;
if (lflag) {
ul = va_arg (ap, unsigned long);
if (ul == -1) {
cnt_unknown: if (ladjust)
PUTC ('-');
while (--width > 0)
PUTC (' ');
if (! ladjust)
PUTC ('-');
break;
}
if (ul >= -2) {
ul = -3;
neg = '>';
goto nosign;
}
} else {
ul = va_arg (ap, unsigned int);
if (ul == (unsigned short) -1)
goto cnt_unknown;
if (ul >= (unsigned short) -2) {
ul = (unsigned short) -3;
neg = '>';
goto nosign;
}
}
goto nosign;
case 'u':
ul = lflag ? va_arg (ap, unsigned long) :
va_arg (ap, unsigned int);
base = 10;
goto nosign;
case 'x':
case 'X':
ul = lflag ? va_arg (ap, unsigned long) :
va_arg (ap, unsigned int);
base = 16;
uppercase = (c == 'X');
goto nosign;
case 'z':
case 'Z':
ul = lflag ? va_arg (ap, unsigned long) :
sign ? (unsigned long) va_arg (ap, int) :
va_arg (ap, unsigned int);
base = 16;
uppercase = (c == 'Z');
goto number;
nosign: sign = 0;
number: if (sign && ((long) ul != 0L)) {
if ((long) ul < 0L) {
neg = '-';
ul = -(long) ul;
} else if (sign < 0)
neg = '+';
}
if (dwidth >= (int) sizeof(nbuf)) {
extrazeros = dwidth - sizeof(nbuf) + 1;
dwidth = sizeof(nbuf) - 1;
}
s = ksprintn (nbuf, ul, base, dwidth, &size);
if (sharpflag && ul != 0) {
if (base == 8)
size++;
else if (base == 16)
size += 2;
}
if (neg)
size++;
if (! ladjust && width && padding == ' ' &&
(width -= size) > 0)
do {
PUTC (' ');
} while (--width > 0);
if (neg)
PUTC (neg);
if (sharpflag && ul != 0) {
if (base == 8) {
PUTC ('0');
} else if (base == 16) {
PUTC ('0');
PUTC (uppercase ? 'X' : 'x');
}
}
if (extrazeros)
do {
PUTC ('0');
} while (--extrazeros > 0);
if (! ladjust && width && (width -= size) > 0)
do {
PUTC (padding);
} while (--width > 0);
for (; *s; --s) {
if (uppercase && *s>='a' && *s<='z') {
PUTC (*s + 'A' - 'a');
} else {
PUTC (*s);
}
}
if (ladjust && width && (width -= size) > 0)
do {
PUTC (' ');
} while (--width > 0);
break;
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G': {
double d;
unsigned long *l = (unsigned long *) &d;
x.ap32 = ap;
if ( x.u32 & 4) {
l[0]= va_arg(ap, unsigned long);
l[1]= va_arg(ap, unsigned long);
}
else {
l[0]= va_arg(ap, unsigned long);
l[0]= va_arg(ap, unsigned long);
l[1]= va_arg(ap, unsigned long);
}
/*
* don't do unrealistic precision; just pad it with
* zeroes later, so buffer size stays rational.
*/
if (dwidth > DBL_DIG) {
if ((c != 'g' && c != 'G') || sharpflag)
extrazeros = dwidth - DBL_DIG;
dwidth = DBL_DIG;
} else if (dwidth == -1) {
dwidth = (lflag ? DBL_DIG : FLT_DIG);
}
/*
* softsign avoids negative 0 if d is < 0 and
* no significant digits will be shown
*/
if (d < 0) {
neg = 1;
d = -d;
}
/*
* cvt may have to round up past the "start" of the
* buffer, i.e. ``intf("%.2f", (double)9.999);'';
* if the first char isn't NULL, it did.
*/
if (isnan (d) || isinf (d)) {
strcpy ((char*)nbuf, isnan (d) ? "NaN" : "Inf");
size = 3;
extrazeros = 0;
s = nbuf;
} else {
*nbuf = 0;
size = cvt (d, dwidth, sharpflag, &neg, c,
nbuf, nbuf + sizeof(nbuf) - 1);
if (*nbuf) {
s = nbuf;
nbuf [size] = 0;
} else {
s = nbuf + 1;
nbuf [size + 1] = 0;
}
}
if (neg)
size++;
if (! ladjust && width && padding == ' ' &&
(width -= size) > 0)
do {
PUTC (' ');
} while (--width > 0);
if (neg)
PUTC ('-');
if (! ladjust && width && (width -= size) > 0)
do {
PUTC (padding);
} while (--width > 0);
for (; *s; ++s) {
if (extrazeros && (*s == 'e' || *s == 'E'))
do {
PUTC ('0');
} while (--extrazeros > 0);
PUTC (*s);
}
if (extrazeros)
do {
PUTC ('0');
} while (--extrazeros > 0);
if (ladjust && width && (width -= size) > 0)
do {
PUTC (' ');
} while (--width > 0);
break;
}
default:
PUTC ('%');
if (lflag)
PUTC ('l');
PUTC (c);
break;
}
}
}
/*
* Put a NUL-terminated ASCII number (base <= 16) in a buffer in reverse
* order; return an optional length and a pointer to the last character
* written in the buffer (i.e., the first character of the string).
* The buffer pointed to by `nbuf' must have length >= MAXNBUF.
*/
static unsigned char *
ksprintn (unsigned char *nbuf, unsigned long ul, unsigned char base, int width,
unsigned char *lenp)
{
unsigned char *p;
p = nbuf;
*p = 0;
for (;;) {
*++p = mkhex (ul % base);
ul /= base;
if (--width > 0)
continue;
if (! ul)
break;
}
if (lenp)
*lenp = p - nbuf;
return (p);
}
static unsigned char
mkhex (unsigned char ch)
{
ch &= 15;
if (ch > 9)
return ch + 'a' - 10;
return ch + '0';
}
static unsigned char *
cvtround (double fract, int *exp, unsigned char *start, unsigned char *end, unsigned char ch,
unsigned char *negp)
{
double tmp;
if (fract) {
modf (fract * 10, &tmp);
} else {
tmp = ch - '0';
}
if (tmp > 4) {
for (;; --end) {
if (*end == '.') {
--end;
}
if (++*end <= '9') {
break;
}
*end = '0';
if (end == start) {
if (exp) { /* e/E; increment exponent */
*end = '1';
++*exp;
} else { /* f; add extra digit */
*--end = '1';
--start;
}
break;
}
}
} else if (*negp) {
/*
* ``"%.3f", (double)-0.0004'' gives you a negative 0.
*/
for (;; --end) {
if (*end == '.') {
--end;
}
if (*end != '0') {
break;
}
if (end == start) {
*negp = 0;
}
}
}
return start;
}
static unsigned char *
exponent (unsigned char *p, int exp, unsigned char fmtch)
{
unsigned char expbuf [8], *t;
*p++ = fmtch;
if (exp < 0) {
exp = -exp;
*p++ = '-';
} else {
*p++ = '+';
}
t = expbuf + sizeof(expbuf);
if (exp > 9) {
do {
*--t = exp % 10 + '0';
} while ((exp /= 10) > 9);
*--t = exp + '0';
for (; t < expbuf + sizeof(expbuf); *p++ = *t++)
continue;
} else {
*p++ = '0';
*p++ = exp + '0';
}
return p;
}
static int
cvt (double number, int prec, int sharpflag, unsigned char *negp, unsigned char fmtch,
unsigned char *startp, unsigned char *endp)
{
unsigned char *p, *t;
double fract;
int dotrim, expcnt, gformat;
double integer, tmp;
expcnt = 0;
dotrim = expcnt = gformat = 0;
fract = modf (number, &integer);
/*
* get an extra slot for rounding
*/
t = ++startp;
/*
* get integer portion of number; put into the end of the buffer; the
* .01 is added for modf (356.0 / 10, &integer) returning .59999999...
*/
p = endp - 1;
for (; integer && p >= startp; ++expcnt) {
tmp = modf(integer * 0.1L , &integer);
*p-- = (int)((tmp + .01L) * 10) + '0';
}
switch (fmtch) {
case 'f':
/* reverse integer into beginning of buffer */
if (expcnt) {
for (; ++p < endp; *t++ = *p);
} else {
*t++ = '0';
}
/*
* if precision required or alternate flag set, add in a
* decimal point.
*/
if (prec || sharpflag) {
*t++ = '.';
}
/*
* if requires more precision and some fraction left
*/
if (fract) {
if (prec) {
do {
fract = modf (fract * 10, &tmp);
*t++ = (int)tmp + '0';
} while (--prec && fract);
}
if (fract) {
startp = cvtround (fract, 0, startp,
t - 1, '0', negp);
}
}
for (; prec--; *t++ = '0');
break;
case 'e':
case 'E':
eformat: if (expcnt) {
*t++ = *++p;
if (prec || sharpflag) {
*t++ = '.';
}
/*
* if requires more precision and some integer left
*/
for (; prec && ++p < endp; --prec) {
*t++ = *p;
}
/*
* if done precision and more of the integer component,
* round using it; adjust fract so we don't re-round
* later.
*/
if (! prec && ++p < endp) {
fract = 0;
startp = cvtround (0, &expcnt, startp,
t - 1, *p, negp);
}
/*
* adjust expcnt for digit in front of decimal
*/
--expcnt;
}
/*
* until first fractional digit, decrement exponent
*/
else if (fract) {
/*
* adjust expcnt for digit in front of decimal
*/
for (expcnt = -1;; --expcnt) {
fract = modf (fract * 10, &tmp);
if (tmp) {
break;
}
}
*t++ = (int)tmp + '0';
if (prec || sharpflag) {
*t++ = '.';
}
} else {
*t++ = '0';
if (prec || sharpflag) {
*t++ = '.';
}
}
/*
* if requires more precision and some fraction left
*/
if (fract) {
if (prec) {
do {
fract = modf (fract * 10, &tmp);
*t++ = (int)tmp + '0';
} while (--prec && fract);
}
if (fract) {
startp = cvtround (fract, &expcnt, startp,
t - 1, '0', negp);
}
}
/*
* if requires more precision
*/
for (; prec--; *t++ = '0');
/*
* unless alternate flag, trim any g/G format trailing 0's
*/
if (gformat && ! sharpflag) {
while (t > startp && *--t == '0');
if (*t == '.') {
--t;
}
++t;
}
t = exponent (t, expcnt, fmtch);
break;
case 'g':
case 'G':
/*
* a precision of 0 is treated as a precision of 1
*/
if (!prec) {
++prec;
}
/*
* ``The style used depends on the value converted; style e
* will be used only if the exponent resulting from the
* conversion is less than -4 or greater than the precision.''
* -- ANSI X3J11
*/
if (expcnt > prec || (! expcnt && fract && fract < .0001)) {
/*
* g/G format counts "significant digits, not digits of
* precision; for the e/E format, this just causes an
* off-by-one problem, i.e. g/G considers the digit
* before the decimal point significant and e/E doesn't
* count it as precision.
*/
--prec;
fmtch -= 2; /* G->E, g->e */
gformat = 1;
goto eformat;
}
/*
* reverse integer into beginning of buffer,
* note, decrement precision
*/
if (expcnt) {
for (; ++p < endp; *t++ = *p, --prec);
} else {
*t++ = '0';
}
/*
* if precision required or alternate flag set, add in a
* decimal point. If no digits yet, add in leading 0.
*/
if (prec || sharpflag) {
dotrim = 1;
*t++ = '.';
} else {
dotrim = 0;
}
/*
* if requires more precision and some fraction left
*/
while (prec && fract) {
fract = modf (fract * 10, &tmp);
*t++ = (int)tmp + '0';
prec--;
}
if (fract) {
startp = cvtround (fract, 0, startp, t - 1, '0', negp);
}
/*
* alternate format, adds 0's for precision, else trim 0's
*/
if (sharpflag) {
for (; prec--; *t++ = '0');
} else if (dotrim) {
while (t > startp && *--t == '0');
if (*t != '.') {
++t;
}
}
}
return t - startp;
}