--- /dev/null
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_string.h>
+#include <nxt_sprintf.h>
+
+#include <math.h>
+#include <float.h>
+#include <string.h>
+
+
+/*
+ * Supported formats:
+ *
+ * %[0][width][x][X]O nxt_off_t
+ * %[0][width]T nxt_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 nxt_int_t/nxt_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 nxt_bool_t
+ * %V nxt_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 *
+nxt_sprintf(u_char *buf, u_char *end, const char *fmt, ...)
+{
+ u_char *p;
+ va_list args;
+
+ va_start(args, fmt);
+ p = nxt_vsprintf(buf, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+
+
+/*
+ * nxt_sprintf_t is used:
+ * to pass several parameters of nxt_integer() via single pointer
+ * and to store little used variables of nxt_vsprintf().
+ */
+
+typedef struct {
+ u_char *end;
+ const u_char *hex;
+ uint32_t width;
+ int32_t frac_width;
+ uint8_t max_width;
+ u_char padding;
+} nxt_sprintf_t;
+
+
+static u_char *nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64);
+static u_char *nxt_number(nxt_sprintf_t *spf, u_char *buf, double n);
+
+
+/* A right way of "f == 0.0". */
+#define nxt_double_is_zero(f) (fabs(f) <= FLT_EPSILON)
+
+
+u_char *
+nxt_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;
+ nxt_str_t *v;
+ nxt_uint_t scale, n;
+ nxt_bool_t sign;
+ nxt_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, nxt_str_t *);
+
+ if (nxt_fast_path(v != NULL)) {
+ length = v->length;
+ p = v->start;
+ goto copy;
+ }
+
+ continue;
+
+ case 's':
+ p = va_arg(args, u_char *);
+
+ if (nxt_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 (nxt_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, nxt_off_t);
+ sign = 1;
+ goto number;
+
+ case 'T':
+ i64 = (int64_t) va_arg(args, nxt_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, nxt_int_t);
+ } else {
+ ui64 = (uint64_t) va_arg(args, nxt_uint_t);
+ }
+
+ if (spf.max_width != 0) {
+ spf.width = NXT_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, nxt_bool_t);
+ sign = 0;
+ goto number;
+
+ case 'f':
+ fmt++;
+
+ f = va_arg(args, double);
+
+ if (f < 0) {
+ *buf++ = '-';
+ f = -f;
+ }
+
+ if (nxt_slow_path(isnan(f))) {
+ p = (u_char *) nan;
+ length = nxt_length(nan);
+
+ goto copy;
+
+ } else if (nxt_slow_path(isinf(f))) {
+ p = (u_char *) infinity;
+ length = nxt_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 = nxt_number(&spf, buf, i);
+
+ if (spf.frac_width > 0) {
+
+ if (buf < end) {
+ *buf++ = '.';
+
+ spf.hex = NULL;
+ spf.padding = '0';
+ spf.width = spf.frac_width;
+ buf = nxt_integer(&spf, buf, frac);
+ }
+
+ } else if (spf.frac_width < 0) {
+ f = modf(f, &i);
+
+ if (!nxt_double_is_zero(f) && buf < end) {
+ *buf++ = '.';
+
+ while (!nxt_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 = NXT_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 = nxt_integer(&spf, buf, ui64);
+
+ fmt++;
+ continue;
+
+ copy:
+
+ buf = nxt_cpymem(buf, p, nxt_min((size_t) (end - buf), length));
+ continue;
+ }
+
+ return buf;
+}
+
+
+static u_char *
+nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64)
+{
+ u_char *p, *end;
+ size_t length;
+ u_char temp[NXT_INT64_T_LEN];
+
+ p = temp + NXT_INT64_T_LEN;
+
+ if (spf->hex == NULL) {
+
+#if (NXT_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 /* NXT_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 + NXT_INT64_T_LEN) - p;
+ end = buf + (spf->width - length);
+ end = nxt_min(end, spf->end);
+
+ while (buf < end) {
+ *buf++ = spf->padding;
+ }
+ }
+
+ /* Number copying. */
+
+ length = (temp + NXT_INT64_T_LEN) - p;
+ end = buf + length;
+ end = nxt_min(end, spf->end);
+
+ while (buf < end) {
+ *buf++ = *p++;
+ }
+
+ return buf;
+}
+
+
+static u_char *
+nxt_number(nxt_sprintf_t *spf, u_char *buf, double n)
+{
+ u_char *p, *end;
+ size_t length;
+ u_char temp[NXT_DOUBLE_LEN];
+
+ p = temp + NXT_DOUBLE_LEN;
+
+ do {
+ *(--p) = (u_char) (fmod(n, 10) + '0');
+ n = trunc(n / 10);
+ } while (!nxt_double_is_zero(n));
+
+ /* Zero or space padding. */
+
+ if (spf->width != 0) {
+ length = (temp + NXT_DOUBLE_LEN) - p;
+ end = buf + (spf->width - length);
+ end = nxt_min(end, spf->end);
+
+ while (buf < end) {
+ *buf++ = spf->padding;
+ }
+ }
+
+ /* Number copying. */
+
+ length = (temp + NXT_DOUBLE_LEN) - p;
+
+ end = buf + length;
+ end = nxt_min(end, spf->end);
+
+ while (buf < end) {
+ *buf++ = *p++;
+ }
+
+ return buf;
+}