blob: 1f7358e888fe95e4327888c9d8aac70ba37556aa [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
/*
* Supported formats:
*
* %[0][width][x][X]O njs_off_t
* %[0][width]T njs_time_t
* %[0][width][u][x|X]z ssize_t/size_t
* %[0][width][u][x|X]d int/u_int
* %[0][width][u][x|X]l long
* %[0][width|m][u][x|X]i njs_int_t/njs_uint_t
* %[0][width][u][x|X]D int32_t/uint32_t
* %[0][width][u][x|X]L int64_t/uint64_t
* %[0][width][.width]f double, max valid number fits to %18.15f
*
* %d int
*
* %s null-terminated string
* %*s length and string
*
* %p void *
* %b njs_bool_t
* %V njs_str_t *
* %Z '\0'
* %n '\n'
* %c char
* %% %
*
* Reserved:
* %t ptrdiff_t
* %S null-terminated wchar string
* %C wchar
* %[0][width][u][x|X]Q int128_t/uint128_t
*/
u_char *
njs_sprintf(u_char *buf, u_char *end, const char *fmt, ...)
{
u_char *p;
va_list args;
va_start(args, fmt);
p = njs_vsprintf(buf, end, fmt, args);
va_end(args);
return p;
}
/*
* njs_sprintf_t is used:
* to pass several parameters of njs_integer() via single pointer
* and to store little used variables of njs_vsprintf().
*/
typedef struct {
u_char *end;
const u_char *hex;
uint32_t width;
int32_t frac_width;
uint8_t max_width;
u_char padding;
} njs_sprintf_t;
static u_char *njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64);
static u_char *njs_float(njs_sprintf_t *spf, u_char *buf, double n);
/* A right way of "f == 0.0". */
#define njs_double_is_zero(f) (fabs(f) <= FLT_EPSILON)
u_char *
njs_vsprintf(u_char *buf, u_char *end, const char *fmt, va_list args)
{
u_char *p;
int d;
double f, i;
size_t length;
int64_t i64;
uint64_t ui64, frac;
njs_str_t *v;
njs_uint_t scale, n;
njs_bool_t sign;
njs_sprintf_t spf;
static const u_char hexadecimal[16] = "0123456789abcdef";
static const u_char HEXADECIMAL[16] = "0123456789ABCDEF";
static const u_char nan[] = "[nan]";
static const u_char infinity[] = "[infinity]";
spf.end = end;
while (*fmt != '\0' && buf < end) {
/*
* "buf < end" means that we could copy at least one character:
* a plain character, "%%", "%c", or a minus without test.
*/
if (*fmt != '%') {
*buf++ = *fmt++;
continue;
}
fmt++;
/* Test some often used text formats first. */
switch (*fmt) {
case 'V':
fmt++;
v = va_arg(args, njs_str_t *);
if (njs_fast_path(v != NULL)) {
length = v->length;
p = v->start;
goto copy;
}
continue;
case 's':
p = va_arg(args, u_char *);
if (njs_fast_path(p != NULL)) {
while (*p != '\0' && buf < end) {
*buf++ = *p++;
}
}
fmt++;
continue;
case '*':
length = va_arg(args, size_t);
fmt++;
if (*fmt == 's') {
fmt++;
p = va_arg(args, u_char *);
if (njs_fast_path(p != NULL)) {
goto copy;
}
}
continue;
default:
break;
}
spf.hex = NULL;
spf.width = 0;
spf.frac_width = -1;
spf.max_width = 0;
spf.padding = (*fmt == '0') ? '0' : ' ';
sign = 1;
i64 = 0;
ui64 = 0;
while (*fmt >= '0' && *fmt <= '9') {
spf.width = spf.width * 10 + (*fmt++ - '0');
}
for ( ;; ) {
switch (*fmt) {
case 'u':
sign = 0;
fmt++;
continue;
case 'm':
spf.max_width = 1;
fmt++;
continue;
case 'X':
spf.hex = HEXADECIMAL;
sign = 0;
fmt++;
continue;
case 'x':
spf.hex = hexadecimal;
sign = 0;
fmt++;
continue;
case '.':
fmt++;
spf.frac_width = 0;
while (*fmt >= '0' && *fmt <= '9') {
spf.frac_width = spf.frac_width * 10 + *fmt++ - '0';
}
break;
default:
break;
}
break;
}
switch (*fmt) {
case 'O':
i64 = (int64_t) va_arg(args, njs_off_t);
sign = 1;
goto number;
case 'T':
i64 = (int64_t) va_arg(args, njs_time_t);
sign = 1;
goto number;
case 'z':
if (sign) {
i64 = (int64_t) va_arg(args, ssize_t);
} else {
ui64 = (uint64_t) va_arg(args, size_t);
}
goto number;
case 'i':
if (sign) {
i64 = (int64_t) va_arg(args, njs_int_t);
} else {
ui64 = (uint64_t) va_arg(args, njs_uint_t);
}
if (spf.max_width != 0) {
spf.width = NJS_INT_T_LEN;
}
goto number;
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
goto number;
case 'l':
if (sign) {
i64 = (int64_t) va_arg(args, long);
} else {
ui64 = (uint64_t) va_arg(args, u_long);
}
goto number;
case 'D':
if (sign) {
i64 = (int64_t) va_arg(args, int32_t);
} else {
ui64 = (uint64_t) va_arg(args, uint32_t);
}
goto number;
case 'L':
if (sign) {
i64 = va_arg(args, int64_t);
} else {
ui64 = va_arg(args, uint64_t);
}
goto number;
case 'b':
ui64 = (uint64_t) va_arg(args, njs_bool_t);
sign = 0;
goto number;
case 'f':
fmt++;
f = va_arg(args, double);
if (f < 0) {
*buf++ = '-';
f = -f;
}
if (njs_slow_path(isnan(f))) {
p = (u_char *) nan;
length = njs_length(nan);
goto copy;
} else if (njs_slow_path(isinf(f))) {
p = (u_char *) infinity;
length = njs_length(infinity);
goto copy;
}
(void) modf(f, &i);
frac = 0;
if (spf.frac_width > 0) {
scale = 1;
for (n = spf.frac_width; n != 0; n--) {
scale *= 10;
}
frac = (uint64_t) ((f - i) * scale + 0.5);
if (frac == scale) {
i += 1;
frac = 0;
}
}
buf = njs_float(&spf, buf, i);
if (spf.frac_width > 0) {
if (buf < end) {
*buf++ = '.';
spf.hex = NULL;
spf.padding = '0';
spf.width = spf.frac_width;
buf = njs_integer(&spf, buf, frac);
}
} else if (spf.frac_width < 0) {
f = modf(f, &i);
if (!njs_double_is_zero(f) && buf < end) {
*buf++ = '.';
while (!njs_double_is_zero(f) && buf < end) {
f *= 10;
f = modf(f, &i);
*buf++ = (u_char) i + '0';
}
}
}
continue;
case 'p':
ui64 = (uintptr_t) va_arg(args, void *);
sign = 0;
spf.hex = HEXADECIMAL;
/*
* spf.width = NJS_PTR_SIZE * 2;
* spf.padding = '0';
*/
goto number;
case 'c':
d = va_arg(args, int);
*buf++ = (u_char) (d & 0xFF);
fmt++;
continue;
case 'Z':
*buf++ = '\0';
fmt++;
continue;
case 'n':
*buf++ = '\n';
fmt++;
continue;
case '%':
*buf++ = '%';
fmt++;
continue;
default:
*buf++ = *fmt++;
continue;
}
number:
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
buf = njs_integer(&spf, buf, ui64);
fmt++;
continue;
copy:
buf = njs_cpymem(buf, p, njs_min((size_t) (end - buf), length));
continue;
}
return buf;
}
static u_char *
njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64)
{
u_char *p, *end;
size_t length;
u_char temp[NJS_INT64_T_LEN];
p = temp + NJS_INT64_T_LEN;
if (spf->hex == NULL) {
#if (NJS_32BIT)
for ( ;; ) {
u_char *start;
uint32_t ui32;
/*
* 32-bit platforms usually lack hardware support of 64-bit
* division and remainder operations. For this reason C compiler
* adds calls to the runtime library functions which provides
* these operations. These functions usually have about hundred
* lines of code.
*
* For 32-bit numbers and some constant divisors GCC, Clang and
* other compilers can use inlined multiplications and shifts
* which are faster than division or remainder operations.
* For example, unsigned "ui32 / 10" is compiled to
*
* ((uint64_t) ui32 * 0xCCCCCCCD) >> 35
*
* So a 64-bit number is split to parts by 10^9. The parts fit
* to 32 bits and are processed separately as 32-bit numbers. A
* number of 64-bit division/remainder operations is significantly
* decreased depending on the 64-bit number's value, it is
* 0 if the 64-bit value is less than 4294967296,
* 1 if the 64-bit value is greater than 4294967295
* and less than 4294967296000000000,
* 2 otherwise.
*/
if (ui64 <= 0xFFFFFFFF) {
ui32 = (uint32_t) ui64;
start = NULL;
} else {
ui32 = (uint32_t) (ui64 % 1000000000);
start = p - 9;
}
do {
*(--p) = (u_char) (ui32 % 10 + '0');
ui32 /= 10;
} while (ui32 != 0);
if (start == NULL) {
break;
}
/* Add leading zeros of part. */
while (p > start) {
*(--p) = '0';
}
ui64 /= 1000000000;
}
#else /* NJS_64BIT */
do {
*(--p) = (u_char) (ui64 % 10 + '0');
ui64 /= 10;
} while (ui64 != 0);
#endif
} else {
do {
*(--p) = spf->hex[ui64 & 0xF];
ui64 >>= 4;
} while (ui64 != 0);
}
/* Zero or space padding. */
if (spf->width != 0) {
length = (temp + NJS_INT64_T_LEN) - p;
end = buf + (spf->width - length);
end = njs_min(end, spf->end);
while (buf < end) {
*buf++ = spf->padding;
}
}
/* Number copying. */
length = (temp + NJS_INT64_T_LEN) - p;
end = buf + length;
end = njs_min(end, spf->end);
while (buf < end) {
*buf++ = *p++;
}
return buf;
}
static u_char *
njs_float(njs_sprintf_t *spf, u_char *buf, double n)
{
u_char *p, *end;
size_t length;
u_char temp[NJS_DOUBLE_LEN];
p = temp + NJS_DOUBLE_LEN;
do {
*(--p) = (u_char) (fmod(n, 10) + '0');
n = trunc(n / 10);
} while (!njs_double_is_zero(n));
/* Zero or space padding. */
if (spf->width != 0) {
length = (temp + NJS_DOUBLE_LEN) - p;
end = buf + (spf->width - length);
end = njs_min(end, spf->end);
while (buf < end) {
*buf++ = spf->padding;
}
}
/* Number copying. */
length = (temp + NJS_DOUBLE_LEN) - p;
end = buf + length;
end = njs_min(end, spf->end);
while (buf < end) {
*buf++ = *p++;
}
return buf;
}
NJS_EXPORT
int njs_dprint(int fd, u_char *buf, size_t size)
{
return write(fd, buf, size);
}
int
njs_dprintf(int fd, const char *fmt, ...)
{
size_t size;
u_char text[2048], *p;
va_list args;
va_start(args, fmt);
p = njs_vsprintf(text, text + sizeof(text), fmt, args);
va_end(args);
size = p - text;
return write(fd, text, size);
}