/*
* Copyright (c) 2010 Richard Braun.
*
* 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 3 of the License, 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, see .
*/
#include
#include
#include
#include
#include
/*
* Formatting flags.
*
* FORMAT_LOWER must be 0x20 as it is OR'd with digits, eg.
* '0': 0x30 | 0x20 => 0x30 ('0')
* 'A': 0x41 | 0x20 => 0x61 ('a')
*/
#define SPRINTF_FORMAT_ALT_FORM 0x01
#define SPRINTF_FORMAT_ZERO_PAD 0x02
#define SPRINTF_FORMAT_LEFT_JUSTIFY 0x04
#define SPRINTF_FORMAT_BLANK 0x08
#define SPRINTF_FORMAT_SIGN 0x10
#define SPRINTF_FORMAT_LOWER 0x20
#define SPRINTF_FORMAT_CONV_SIGNED 0x40
enum {
SPRINTF_MODIFIER_NONE,
SPRINTF_MODIFIER_CHAR,
SPRINTF_MODIFIER_SHORT,
SPRINTF_MODIFIER_LONG,
SPRINTF_MODIFIER_LONGLONG,
SPRINTF_MODIFIER_PTR, /* Used only for %p */
SPRINTF_MODIFIER_SIZE,
SPRINTF_MODIFIER_PTRDIFF
};
enum {
SPRINTF_SPECIFIER_INVALID,
SPRINTF_SPECIFIER_INT,
SPRINTF_SPECIFIER_CHAR,
SPRINTF_SPECIFIER_STR,
SPRINTF_SPECIFIER_NRCHARS,
SPRINTF_SPECIFIER_PERCENT
};
/*
* Size for the temporary number buffer. The minimum base is 8 so 3 bits
* are consumed per digit. Add one to round up. The conversion algorithm
* doesn't use the null byte.
*/
#define SPRINTF_MAX_NUM_SIZE (((sizeof(uint64_t) * CHAR_BIT) / 3) + 1)
/*
* Special size for vsnprintf(), used by sprintf()/vsprintf() when the
* buffer size is unknown.
*/
#define SPRINTF_NOLIMIT ((size_t)-1)
static const char sprintf_digits[] = "0123456789ABCDEF";
static inline char *
sprintf_putchar(char *str, char *end, char c)
{
if (str < end)
*str = c;
str++;
return str;
}
static inline int
sprintf_isdigit(char c)
{
return (c >= '0') && (c <= '9');
}
int
sprintf(char *str, const char *format, ...)
{
va_list ap;
int length;
va_start(ap, format);
length = vsprintf(str, format, ap);
va_end(ap);
return length;
}
int
vsprintf(char *str, const char *format, va_list ap)
{
return vsnprintf(str, SPRINTF_NOLIMIT, format, ap);
}
int
snprintf(char *str, size_t size, const char *format, ...)
{
va_list ap;
int length;
va_start(ap, format);
length = vsnprintf(str, size, format, ap);
va_end(ap);
return length;
}
int
vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
unsigned long long n;
int i, len, found, flags, width, precision, modifier, specifier, shift;
unsigned char r, base, mask;
char c, *s, *start, *end, sign, tmp[SPRINTF_MAX_NUM_SIZE];
start = str;
if (size == 0)
end = NULL;
else if (size == SPRINTF_NOLIMIT)
end = (char *)-1;
else
end = start + size - 1;
while ((c = *format) != '\0') {
if (c != '%') {
str = sprintf_putchar(str, end, c);
format++;
continue;
}
/* Flags */
found = 1;
flags = 0;
do {
format++;
c = *format;
switch (c) {
case '#':
flags |= SPRINTF_FORMAT_ALT_FORM;
break;
case '0':
flags |= SPRINTF_FORMAT_ZERO_PAD;
break;
case '-':
flags |= SPRINTF_FORMAT_LEFT_JUSTIFY;
break;
case ' ':
flags |= SPRINTF_FORMAT_BLANK;
break;
case '+':
flags |= SPRINTF_FORMAT_SIGN;
break;
default:
found = 0;
break;
}
} while (found);
/* Width */
if (sprintf_isdigit(c)) {
width = 0;
while (sprintf_isdigit(c)) {
width = width * 10 + (c - '0');
format++;
c = *format;
}
} else if (c == '*') {
width = va_arg(ap, int);
if (width < 0) {
flags |= SPRINTF_FORMAT_LEFT_JUSTIFY;
width = -width;
}
format++;
c = *format;
} else {
width = 0;
}
/* Precision */
if (c == '.') {
format++;
c = *format;
if (sprintf_isdigit(c)) {
precision = 0;
while (sprintf_isdigit(c)) {
precision = precision * 10 + (c - '0');
format++;
c = *format;
}
} else if (c == '*') {
precision = va_arg(ap, int);
if (precision < 0)
precision = 0;
format++;
c = *format;
} else {
precision = 0;
}
} else {
/* precision is >= 0 only if explicit */
precision = -1;
}
/* Length modifier */
switch (c) {
case 'h':
case 'l':
format++;
if (c == *format) {
modifier = (c == 'h')
? SPRINTF_MODIFIER_CHAR
: SPRINTF_MODIFIER_LONGLONG;
goto skip_modifier;
} else {
modifier = (c == 'h')
? SPRINTF_MODIFIER_SHORT
: SPRINTF_MODIFIER_LONG;
c = *format;
}
break;
case 'z':
modifier = SPRINTF_MODIFIER_SIZE;
goto skip_modifier;
case 't':
modifier = SPRINTF_MODIFIER_PTRDIFF;
skip_modifier:
format++;
c = *format;
break;
default:
modifier = SPRINTF_MODIFIER_NONE;
break;
}
/* Specifier */
switch (c) {
case 'd':
case 'i':
flags |= SPRINTF_FORMAT_CONV_SIGNED;
case 'u':
base = 10;
goto integer;
case 'o':
base = 8;
goto integer;
case 'p':
flags |= SPRINTF_FORMAT_ALT_FORM;
modifier = SPRINTF_MODIFIER_PTR;
case 'x':
flags |= SPRINTF_FORMAT_LOWER;
case 'X':
base = 16;
integer:
specifier = SPRINTF_SPECIFIER_INT;
break;
case 'c':
specifier = SPRINTF_SPECIFIER_CHAR;
break;
case 's':
specifier = SPRINTF_SPECIFIER_STR;
break;
case 'n':
specifier = SPRINTF_SPECIFIER_NRCHARS;
break;
case '%':
specifier = SPRINTF_SPECIFIER_PERCENT;
break;
default:
specifier = SPRINTF_SPECIFIER_INVALID;
break;
}
/* Output */
switch (specifier) {
case SPRINTF_SPECIFIER_INT:
switch (modifier) {
case SPRINTF_MODIFIER_CHAR:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = (signed char)va_arg(ap, int);
else
n = (unsigned char)va_arg(ap, int);
break;
case SPRINTF_MODIFIER_SHORT:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = (short)va_arg(ap, int);
else
n = (unsigned short)va_arg(ap, int);
break;
case SPRINTF_MODIFIER_LONG:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = va_arg(ap, long);
else
n = va_arg(ap, unsigned long);
break;
case SPRINTF_MODIFIER_LONGLONG:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = va_arg(ap, long long);
else
n = va_arg(ap, unsigned long long);
break;
case SPRINTF_MODIFIER_PTR:
n = (unsigned long)va_arg(ap, void *);
break;
case SPRINTF_MODIFIER_SIZE:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = va_arg(ap, ssize_t);
else
n = va_arg(ap, size_t);
break;
case SPRINTF_MODIFIER_PTRDIFF:
n = va_arg(ap, ptrdiff_t);
break;
default:
if (flags & SPRINTF_FORMAT_CONV_SIGNED)
n = va_arg(ap, int);
else
n = va_arg(ap, unsigned int);
break;
}
if ((flags & SPRINTF_FORMAT_LEFT_JUSTIFY) || (precision >= 0))
flags &= ~SPRINTF_FORMAT_ZERO_PAD;
sign = 0;
if (flags & SPRINTF_FORMAT_ALT_FORM) {
/* '0' for octal */
width--;
/* '0x' or '0X' for hexadecimal */
if (base == 16)
width--;
} else if (flags & SPRINTF_FORMAT_CONV_SIGNED) {
if ((long long)n < 0) {
sign = '-';
width--;
n = -(long long)n;
} else if (flags & SPRINTF_FORMAT_SIGN) {
/* SPRINTF_FORMAT_SIGN must precede SPRINTF_FORMAT_BLANK. */
sign = '+';
width--;
} else if (flags & SPRINTF_FORMAT_BLANK) {
sign = ' ';
width--;
}
}
/* Conversion, in reverse order */
i = 0;
if (n == 0) {
if (precision != 0)
tmp[i++] = '0';
} else if (base == 10) {
/*
* Try to avoid 64 bits operations if the processor doesn't
* support them. Note that even when using modulus and
* division operators close to each other, the compiler will
* forge two calls to __udivdi3() and __umoddi3() instead of
* one to __udivmoddi3(), whereas processor instructions are
* generally correctly used once, giving both the remainder
* and the quotient, through plain or reciprocal division.
*/
#ifndef __LP64__
if (modifier == SPRINTF_MODIFIER_LONGLONG) {
#endif /* __LP64__ */
do {
r = n % 10;
n /= 10;
tmp[i++] = sprintf_digits[r];
} while (n != 0);
#ifndef __LP64__
} else {
unsigned long m;
m = (unsigned long)n;
do {
r = m % 10;
m /= 10;
tmp[i++] = sprintf_digits[r];
} while (m != 0);
}
#endif /* __LP64__ */
} else {
mask = base - 1;
shift = (base == 8) ? 3 : 4;
do {
r = (unsigned char)n & mask;
n >>= shift;
tmp[i++] = sprintf_digits[r]
| (flags & SPRINTF_FORMAT_LOWER);
} while (n != 0);
}
if (i > precision)
precision = i;
width -= precision;
if (!(flags & (SPRINTF_FORMAT_LEFT_JUSTIFY
| SPRINTF_FORMAT_ZERO_PAD)))
while (width-- > 0)
str = sprintf_putchar(str, end, ' ');
if (flags & SPRINTF_FORMAT_ALT_FORM) {
str = sprintf_putchar(str, end, '0');
if (base == 16)
str = sprintf_putchar(str, end,
'X' | (flags & SPRINTF_FORMAT_LOWER));
} else if (sign) {
str = sprintf_putchar(str, end, sign);
}
if (!(flags & SPRINTF_FORMAT_LEFT_JUSTIFY)) {
c = (flags & SPRINTF_FORMAT_ZERO_PAD) ? '0' : ' ';
while (width-- > 0)
str = sprintf_putchar(str, end, c);
}
while (i < precision--)
str = sprintf_putchar(str, end, '0');
while (i-- > 0)
str = sprintf_putchar(str, end, tmp[i]);
while (width-- > 0)
str = sprintf_putchar(str, end, ' ');
break;
case SPRINTF_SPECIFIER_CHAR:
c = (unsigned char)va_arg(ap, int);
if (!(flags & SPRINTF_FORMAT_LEFT_JUSTIFY))
while (--width > 0)
str = sprintf_putchar(str, end, ' ');
str = sprintf_putchar(str, end, c);
while (--width > 0)
str = sprintf_putchar(str, end, ' ');
break;
case SPRINTF_SPECIFIER_STR:
s = va_arg(ap, char *);
if (s == NULL)
s = "(null)";
len = 0;
for (len = 0; s[len] != '\0'; len++)
if (len == precision)
break;
if (!(flags & SPRINTF_FORMAT_LEFT_JUSTIFY))
while (len < width--)
str = sprintf_putchar(str, end, ' ');
for (i = 0; i < len; i++) {
str = sprintf_putchar(str, end, *s);
s++;
}
while (len < width--)
str = sprintf_putchar(str, end, ' ');
break;
case SPRINTF_SPECIFIER_NRCHARS:
if (modifier == SPRINTF_MODIFIER_CHAR) {
signed char *ptr = va_arg(ap, signed char *);
*ptr = str - start;
} else if (modifier == SPRINTF_MODIFIER_SHORT) {
short *ptr = va_arg(ap, short *);
*ptr = str - start;
} else if (modifier == SPRINTF_MODIFIER_LONG) {
long *ptr = va_arg(ap, long *);
*ptr = str - start;
} else if (modifier == SPRINTF_MODIFIER_LONGLONG) {
long long *ptr = va_arg(ap, long long *);
*ptr = str - start;
} else if (modifier == SPRINTF_MODIFIER_SIZE) {
ssize_t *ptr = va_arg(ap, ssize_t *);
*ptr = str - start;
} else if (modifier == SPRINTF_MODIFIER_PTRDIFF) {
ptrdiff_t *ptr = va_arg(ap, ptrdiff_t *);
*ptr = str - start;
} else {
int *ptr = va_arg(ap, int *);
*ptr = str - start;
}
break;
case SPRINTF_SPECIFIER_PERCENT:
case SPRINTF_SPECIFIER_INVALID:
str = sprintf_putchar(str, end, '%');
break;
default:
break;
}
if (specifier != SPRINTF_SPECIFIER_INVALID)
format++;
}
if (str < end)
*str = '\0';
else if (end != NULL)
*end = '\0';
return str - start;
}