455 lines
9.6 KiB
C
455 lines
9.6 KiB
C
/*
|
|
* vsnprintf.c
|
|
*
|
|
* vsnprintf(), from which the rest of the printf()
|
|
* family is built
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
|
|
enum flags {
|
|
FL_ZERO = 0x01, /* Zero modifier */
|
|
FL_MINUS = 0x02, /* Minus modifier */
|
|
FL_PLUS = 0x04, /* Plus modifier */
|
|
FL_TICK = 0x08, /* ' modifier */
|
|
FL_SPACE = 0x10, /* Space modifier */
|
|
FL_HASH = 0x20, /* # modifier */
|
|
FL_SIGNED = 0x40, /* Number is signed */
|
|
FL_UPPER = 0x80 /* Upper case digits */
|
|
};
|
|
|
|
/* These may have to be adjusted on certain implementations */
|
|
enum ranks {
|
|
rank_char = -2,
|
|
rank_short = -1,
|
|
rank_int = 0,
|
|
rank_long = 1,
|
|
rank_longlong = 2
|
|
};
|
|
|
|
#define MIN_RANK rank_char
|
|
#define MAX_RANK rank_longlong
|
|
|
|
#define INTMAX_RANK rank_longlong
|
|
#define SIZE_T_RANK rank_long
|
|
#define PTRDIFF_T_RANK rank_long
|
|
|
|
#define EMIT(x) ({ if (o<n){*q++ = (x);} o++; })
|
|
|
|
static size_t
|
|
format_int(char *q, size_t n, uintmax_t val, enum flags flags,
|
|
int base, int width, int prec)
|
|
{
|
|
char *qq;
|
|
size_t o = 0, oo;
|
|
static const char lcdigits[] = "0123456789abcdef";
|
|
static const char ucdigits[] = "0123456789ABCDEF";
|
|
const char *digits;
|
|
uintmax_t tmpval;
|
|
int minus = 0;
|
|
int ndigits = 0, nchars;
|
|
int tickskip, b4tick;
|
|
|
|
/* Select type of digits */
|
|
digits = (flags & FL_UPPER) ? ucdigits : lcdigits;
|
|
|
|
/* If signed, separate out the minus */
|
|
if (flags & FL_SIGNED && (intmax_t) val < 0) {
|
|
minus = 1;
|
|
val = (uintmax_t) (-(intmax_t) val);
|
|
}
|
|
|
|
/* Count the number of digits needed. This returns zero for 0. */
|
|
tmpval = val;
|
|
while (tmpval) {
|
|
tmpval /= base;
|
|
ndigits++;
|
|
}
|
|
|
|
/* Adjust ndigits for size of output */
|
|
|
|
if (flags & FL_HASH && base == 8) {
|
|
if (prec < ndigits + 1)
|
|
prec = ndigits + 1;
|
|
}
|
|
|
|
if (ndigits < prec) {
|
|
ndigits = prec; /* Mandatory number padding */
|
|
} else if (val == 0) {
|
|
ndigits = 1; /* Zero still requires space */
|
|
}
|
|
|
|
/* For ', figure out what the skip should be */
|
|
if (flags & FL_TICK) {
|
|
tickskip = (base == 16) ? 4 : 3;
|
|
} else {
|
|
tickskip = ndigits; /* No tick marks */
|
|
}
|
|
|
|
/* Tick marks aren't digits, but generated by the number converter */
|
|
ndigits += (ndigits - 1) / tickskip;
|
|
|
|
/* Now compute the number of nondigits */
|
|
nchars = ndigits;
|
|
|
|
if (minus || (flags & (FL_PLUS | FL_SPACE)))
|
|
nchars++; /* Need space for sign */
|
|
if ((flags & FL_HASH) && base == 16) {
|
|
nchars += 2; /* Add 0x for hex */
|
|
}
|
|
|
|
/* Emit early space padding */
|
|
if (!(flags & (FL_MINUS | FL_ZERO)) && width > nchars) {
|
|
while (width > nchars) {
|
|
EMIT(' ');
|
|
width--;
|
|
}
|
|
}
|
|
|
|
/* Emit nondigits */
|
|
if (minus)
|
|
EMIT('-');
|
|
else if (flags & FL_PLUS)
|
|
EMIT('+');
|
|
else if (flags & FL_SPACE)
|
|
EMIT(' ');
|
|
|
|
if ((flags & FL_HASH) && base == 16) {
|
|
EMIT('0');
|
|
EMIT((flags & FL_UPPER) ? 'X' : 'x');
|
|
}
|
|
|
|
/* Emit zero padding */
|
|
if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) {
|
|
while (width > nchars) {
|
|
EMIT('0');
|
|
width--;
|
|
}
|
|
}
|
|
|
|
/* Generate the number. This is done from right to left. */
|
|
q += ndigits; /* Advance the pointer to end of number */
|
|
o += ndigits;
|
|
qq = q;
|
|
oo = o; /* Temporary values */
|
|
|
|
b4tick = tickskip;
|
|
while (ndigits > 0) {
|
|
if (!b4tick--) {
|
|
qq--;
|
|
oo--;
|
|
ndigits--;
|
|
if (oo < n)
|
|
*qq = '_';
|
|
b4tick = tickskip - 1;
|
|
}
|
|
qq--;
|
|
oo--;
|
|
ndigits--;
|
|
if (oo < n)
|
|
*qq = digits[val % base];
|
|
val /= base;
|
|
}
|
|
|
|
/* Emit late space padding */
|
|
while ((flags & FL_MINUS) && width > nchars) {
|
|
EMIT(' ');
|
|
width--;
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
int vsnprintf(char *buffer, size_t n, const char *format, va_list ap)
|
|
{
|
|
const char *p = format;
|
|
char ch;
|
|
char *q = buffer;
|
|
size_t o = 0; /* Number of characters output */
|
|
uintmax_t val = 0;
|
|
int rank = rank_int; /* Default rank */
|
|
int width = 0;
|
|
int prec = -1;
|
|
int base;
|
|
size_t sz;
|
|
enum flags flags = 0;
|
|
enum {
|
|
st_normal, /* Ground state */
|
|
st_flags, /* Special flags */
|
|
st_width, /* Field width */
|
|
st_prec, /* Field precision */
|
|
st_modifiers /* Length or conversion modifiers */
|
|
} state = st_normal;
|
|
const char *sarg; /* %s string argument */
|
|
char carg; /* %c char argument */
|
|
int slen; /* String length */
|
|
|
|
while ((ch = *p++)) {
|
|
switch (state) {
|
|
case st_normal:
|
|
if (ch == '%') {
|
|
state = st_flags;
|
|
flags = 0;
|
|
rank = rank_int;
|
|
width = 0;
|
|
prec = -1;
|
|
} else {
|
|
EMIT(ch);
|
|
}
|
|
break;
|
|
|
|
case st_flags:
|
|
switch (ch) {
|
|
case '-':
|
|
flags |= FL_MINUS;
|
|
break;
|
|
case '+':
|
|
flags |= FL_PLUS;
|
|
break;
|
|
case '\'':
|
|
flags |= FL_TICK;
|
|
break;
|
|
case ' ':
|
|
flags |= FL_SPACE;
|
|
break;
|
|
case '#':
|
|
flags |= FL_HASH;
|
|
break;
|
|
case '0':
|
|
flags |= FL_ZERO;
|
|
break;
|
|
default:
|
|
state = st_width;
|
|
p--; /* Process this character again */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case st_width:
|
|
if (ch >= '0' && ch <= '9') {
|
|
width = width * 10 + (ch - '0');
|
|
} else if (ch == '*') {
|
|
width = va_arg(ap, int);
|
|
if (width < 0) {
|
|
width = -width;
|
|
flags |= FL_MINUS;
|
|
}
|
|
} else if (ch == '.') {
|
|
prec = 0; /* Precision given */
|
|
state = st_prec;
|
|
} else {
|
|
state = st_modifiers;
|
|
p--; /* Process this character again */
|
|
}
|
|
break;
|
|
|
|
case st_prec:
|
|
if (ch >= '0' && ch <= '9') {
|
|
prec = prec * 10 + (ch - '0');
|
|
} else if (ch == '*') {
|
|
prec = va_arg(ap, int);
|
|
if (prec < 0)
|
|
prec = -1;
|
|
} else {
|
|
state = st_modifiers;
|
|
p--; /* Process this character again */
|
|
}
|
|
break;
|
|
|
|
case st_modifiers:
|
|
switch (ch) {
|
|
/* Length modifiers - nonterminal sequences */
|
|
case 'h':
|
|
rank--; /* Shorter rank */
|
|
break;
|
|
case 'l':
|
|
rank++; /* Longer rank */
|
|
break;
|
|
case 'j':
|
|
rank = INTMAX_RANK;
|
|
break;
|
|
case 'z':
|
|
rank = SIZE_T_RANK;
|
|
break;
|
|
case 't':
|
|
rank = PTRDIFF_T_RANK;
|
|
break;
|
|
case 'L':
|
|
case 'q':
|
|
rank += 2;
|
|
break;
|
|
default:
|
|
/* Output modifiers - terminal sequences */
|
|
state = st_normal; /* Next state will be normal */
|
|
if (rank < MIN_RANK) /* Canonicalize rank */
|
|
rank = MIN_RANK;
|
|
else if (rank > MAX_RANK)
|
|
rank = MAX_RANK;
|
|
|
|
switch (ch) {
|
|
case 'P': /* Upper case pointer */
|
|
flags |= FL_UPPER;
|
|
/* fall through */
|
|
case 'p': /* Pointer */
|
|
base = 16;
|
|
prec = (CHAR_BIT * sizeof(void *) + 3) / 4;
|
|
flags |= FL_HASH;
|
|
val = (uintmax_t) (uintptr_t) va_arg(ap, void *);
|
|
goto is_integer;
|
|
|
|
case 'd': /* Signed decimal output */
|
|
case 'i':
|
|
base = 10;
|
|
flags |= FL_SIGNED;
|
|
switch (rank) {
|
|
case rank_char:
|
|
/* Yes, all these casts are needed... */
|
|
val =
|
|
(uintmax_t) (intmax_t) (signed char)va_arg(ap,
|
|
signed
|
|
int);
|
|
break;
|
|
case rank_short:
|
|
val =
|
|
(uintmax_t) (intmax_t) (signed short)va_arg(ap,
|
|
signed
|
|
int);
|
|
break;
|
|
case rank_int:
|
|
val = (uintmax_t) (intmax_t) va_arg(ap, signed int);
|
|
break;
|
|
case rank_long:
|
|
val = (uintmax_t) (intmax_t) va_arg(ap, signed long);
|
|
break;
|
|
case rank_longlong:
|
|
val =
|
|
(uintmax_t) (intmax_t) va_arg(ap, signed long long);
|
|
break;
|
|
}
|
|
goto is_integer;
|
|
case 'o': /* Octal */
|
|
base = 8;
|
|
goto is_unsigned;
|
|
case 'u': /* Unsigned decimal */
|
|
base = 10;
|
|
goto is_unsigned;
|
|
case 'X': /* Upper case hexadecimal */
|
|
flags |= FL_UPPER;
|
|
/* fall through */
|
|
case 'x': /* Hexadecimal */
|
|
base = 16;
|
|
goto is_unsigned;
|
|
|
|
is_unsigned:
|
|
switch (rank) {
|
|
case rank_char:
|
|
val =
|
|
(uintmax_t) (unsigned char)va_arg(ap, unsigned int);
|
|
break;
|
|
case rank_short:
|
|
val =
|
|
(uintmax_t) (unsigned short)va_arg(ap,
|
|
unsigned int);
|
|
break;
|
|
case rank_int:
|
|
val = (uintmax_t) va_arg(ap, unsigned int);
|
|
break;
|
|
case rank_long:
|
|
val = (uintmax_t) va_arg(ap, unsigned long);
|
|
break;
|
|
case rank_longlong:
|
|
val = (uintmax_t) va_arg(ap, unsigned long long);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
|
|
is_integer:
|
|
sz = format_int(q, (o < n) ? n - o : 0, val, flags, base,
|
|
width, prec);
|
|
q += sz;
|
|
o += sz;
|
|
break;
|
|
|
|
case 'c': /* Character */
|
|
carg = (char)va_arg(ap, int);
|
|
sarg = &carg;
|
|
slen = 1;
|
|
goto is_string;
|
|
case 's': /* String */
|
|
sarg = va_arg(ap, const char *);
|
|
sarg = sarg ? sarg : "(null)";
|
|
slen = strlen(sarg);
|
|
goto is_string;
|
|
|
|
is_string:
|
|
{
|
|
char sch;
|
|
int i;
|
|
|
|
if (prec != -1 && slen > prec)
|
|
slen = prec;
|
|
|
|
if (width > slen && !(flags & FL_MINUS)) {
|
|
char pad = (flags & FL_ZERO) ? '0' : ' ';
|
|
while (width > slen) {
|
|
EMIT(pad);
|
|
width--;
|
|
}
|
|
}
|
|
for (i = slen; i; i--) {
|
|
sch = *sarg++;
|
|
EMIT(sch);
|
|
}
|
|
if (width > slen && (flags & FL_MINUS)) {
|
|
while (width > slen) {
|
|
EMIT(' ');
|
|
width--;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'n': /* Output the number of characters written */
|
|
{
|
|
switch (rank) {
|
|
case rank_char:
|
|
*va_arg(ap, signed char *) = o;
|
|
break;
|
|
case rank_short:
|
|
*va_arg(ap, signed short *) = o;
|
|
break;
|
|
case rank_int:
|
|
*va_arg(ap, signed int *) = o;
|
|
break;
|
|
case rank_long:
|
|
*va_arg(ap, signed long *) = o;
|
|
break;
|
|
case rank_longlong:
|
|
*va_arg(ap, signed long long *) = o;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: /* Anything else, including % */
|
|
EMIT(ch);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Null-terminate the string */
|
|
if (o < n)
|
|
*q = '\0'; /* No overflow */
|
|
else if (n > 0)
|
|
buffer[n - 1] = '\0'; /* Overflow - terminate at end of buffer */
|
|
|
|
return o;
|
|
}
|