| /** |
| * @file fmt/print.c Formatted printing |
| * |
| * Copyright (C) 2010 Creytiv.com |
| */ |
| #include <string.h> |
| #include <re_types.h> |
| #include <re_sa.h> |
| #include <re_fmt.h> |
| #include <re_mem.h> |
| #include <math.h> |
| #ifdef _MSC_VER |
| #include <float.h> |
| #ifndef isinf |
| #define isinf(d) (!_finite(d)) |
| #endif |
| #ifndef isnan |
| #define isnan(d) _isnan(d) |
| #endif |
| #endif |
| #ifdef SOLARIS |
| #include <ieeefp.h> |
| #undef isinf |
| #define isinf(a) (fpclass((a)) == FP_NINF || fpclass((a)) == FP_PINF) |
| #undef isnan |
| #define isnan(a) isnand((a)) |
| #endif |
| |
| |
| enum length_modifier { |
| LENMOD_NONE = 0, |
| LENMOD_LONG = 1, |
| LENMOD_LONG_LONG = 2, |
| LENMOD_SIZE = 42, |
| }; |
| |
| enum { |
| DEC_SIZE = 42, |
| NUM_SIZE = 64 |
| }; |
| |
| static const char prfx_neg[] = "-"; |
| static const char prfx_hex[] = "0x"; |
| static const char str_nil[] = "(nil)"; |
| |
| |
| static int write_padded(const char *p, size_t sz, size_t pad, char pch, |
| bool plr, const char *prfx, re_vprintf_h *vph, |
| void *arg) |
| { |
| const size_t prfx_len = str_len(prfx); |
| int err = 0; |
| |
| pad -= MIN(pad, prfx_len); |
| |
| if (prfx && pch == '0') |
| err |= vph(prfx, prfx_len, arg); |
| |
| while (!plr && (pad-- > sz)) |
| err |= vph(&pch, 1, arg); |
| |
| if (prfx && pch != '0') |
| err |= vph(prfx, prfx_len, arg); |
| |
| if (p && sz) |
| err |= vph(p, sz, arg); |
| |
| while (plr && pad-- > sz) |
| err |= vph(&pch, 1, arg); |
| |
| return err; |
| } |
| |
| |
| static uint32_t local_itoa(char *buf, uint64_t n, uint8_t base, bool uc) |
| { |
| char c, *p = buf + NUM_SIZE; |
| uint32_t len = 1; |
| const char a = uc ? 'A' : 'a'; |
| |
| *--p = '\0'; |
| do { |
| const uint64_t dv = n / base; |
| const uint64_t mul = dv * base; |
| |
| c = (char)(n - mul); |
| |
| if (c < 10) |
| *--p = '0' + c; |
| else |
| *--p = a + (c - 10); |
| |
| n = dv; |
| ++len; |
| |
| } while (n != 0); |
| |
| memmove(buf, p, len); |
| |
| return len - 1; |
| } |
| |
| |
| static size_t local_ftoa(char *buf, double n, size_t dp) |
| { |
| char *p = buf; |
| long long a = (long long)n; |
| double b = n - (double)a; |
| |
| b = (b < 0) ? -b : b; |
| |
| /* integral part */ |
| p += local_itoa(p, (a < 0) ? -a : a, 10, false); |
| |
| *p++ = '.'; |
| |
| /* decimal digits */ |
| while (dp--) { |
| char v; |
| |
| b *= 10; |
| v = (char)b; |
| b -= v; |
| |
| *p++ = '0' + (char)v; |
| } |
| |
| *p = '\0'; |
| |
| return p - buf; |
| } |
| |
| |
| /** |
| * Print a formatted string |
| * |
| * @param fmt Formatted string |
| * @param ap Variable argument |
| * @param vph Print handler |
| * @param arg Handler argument |
| * |
| * @return 0 if success, otherwise errorcode |
| * |
| * Extensions: |
| * |
| * <pre> |
| * %b (char *, size_t) Buffer string with pointer and length |
| * %r (struct pl) Pointer-length object |
| * %w (uint8_t *, size_t) Binary buffer to hexadecimal format |
| * %j (struct sa *) Socket address - address part only |
| * %J (struct sa *) Socket address and port - like 1.2.3.4:1234 |
| * %H (re_printf_h *, void *) Print handler with argument |
| * %v (char *fmt, va_list *) Variable argument list |
| * %m (int) Describe an error code |
| * </pre> |
| * |
| * Reserved for the future: |
| * |
| * %k |
| * %y |
| * |
| */ |
| int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg) |
| { |
| uint8_t base, *bptr; |
| char pch, ch, num[NUM_SIZE], addr[64], msg[256]; |
| enum length_modifier lenmod = LENMOD_NONE; |
| struct re_printf pf; |
| bool fm = false, plr = false; |
| const struct pl *pl; |
| size_t pad = 0, fpad = -1, len, i; |
| const char *str, *p = fmt, *p0 = fmt; |
| const struct sa *sa; |
| re_printf_h *ph; |
| void *ph_arg; |
| va_list *apl; |
| int err = 0; |
| void *ptr; |
| uint64_t n; |
| int64_t sn; |
| bool uc = false; |
| double dbl; |
| |
| if (!fmt || !vph) |
| return EINVAL; |
| |
| pf.vph = vph; |
| pf.arg = arg; |
| |
| for (;*p && !err; p++) { |
| |
| if (!fm) { |
| if (*p != '%') |
| continue; |
| |
| pch = ' '; |
| plr = false; |
| pad = 0; |
| fpad = -1; |
| lenmod = LENMOD_NONE; |
| uc = false; |
| |
| if (p > p0) |
| err |= vph(p0, p - p0, arg); |
| |
| fm = true; |
| continue; |
| } |
| |
| fm = false; |
| base = 10; |
| |
| switch (*p) { |
| |
| case '-': |
| plr = true; |
| fm = true; |
| break; |
| |
| case '.': |
| fpad = pad; |
| pad = 0; |
| fm = true; |
| break; |
| |
| case '%': |
| ch = '%'; |
| |
| err |= vph(&ch, 1, arg); |
| break; |
| |
| case 'b': |
| str = va_arg(ap, const char *); |
| len = va_arg(ap, size_t); |
| |
| err |= write_padded(str, str ? len : 0, pad, ' ', |
| plr, NULL, vph, arg); |
| break; |
| |
| case 'c': |
| ch = va_arg(ap, int); |
| |
| err |= write_padded(&ch, 1, pad, ' ', plr, NULL, |
| vph, arg); |
| break; |
| |
| case 'd': |
| case 'i': |
| switch (lenmod) { |
| |
| case LENMOD_SIZE: |
| sn = va_arg(ap, ssize_t); |
| break; |
| |
| default: |
| case LENMOD_LONG_LONG: |
| sn = va_arg(ap, signed long long); |
| break; |
| |
| case LENMOD_LONG: |
| sn = va_arg(ap, signed long); |
| break; |
| |
| case LENMOD_NONE: |
| sn = va_arg(ap, signed); |
| break; |
| } |
| |
| len = local_itoa(num, (sn < 0) ? -sn : sn, base, |
| false); |
| |
| err |= write_padded(num, len, pad, |
| plr ? ' ' : pch, plr, |
| (sn < 0) ? prfx_neg : NULL, |
| vph, arg); |
| break; |
| |
| case 'f': |
| case 'F': |
| dbl = va_arg(ap, double); |
| |
| if (fpad == (size_t)-1) { |
| fpad = pad; |
| pad = 0; |
| } |
| |
| if (isinf(dbl)) { |
| err |= write_padded("inf", 3, fpad, |
| ' ', plr, NULL, vph, arg); |
| } |
| else if (isnan(dbl)) { |
| err |= write_padded("nan", 3, fpad, |
| ' ', plr, NULL, vph, arg); |
| } |
| else { |
| len = local_ftoa(num, dbl, |
| pad ? min(pad, DEC_SIZE) : 6); |
| |
| err |= write_padded(num, len, fpad, |
| plr ? ' ' : pch, plr, |
| (dbl<0) ? prfx_neg : NULL, |
| vph, arg); |
| } |
| break; |
| |
| case 'H': |
| ph = va_arg(ap, re_printf_h *); |
| ph_arg = va_arg(ap, void *); |
| |
| if (ph) |
| err |= ph(&pf, ph_arg); |
| break; |
| |
| case 'l': |
| ++lenmod; |
| fm = true; |
| break; |
| |
| case 'm': |
| str = str_error(va_arg(ap, int), msg, sizeof(msg)); |
| err |= write_padded(str, str_len(str), pad, |
| ' ', plr, NULL, vph, arg); |
| break; |
| |
| case 'p': |
| ptr = va_arg(ap, void *); |
| |
| if (ptr) { |
| len = local_itoa(num, (unsigned long int)ptr, |
| 16, false); |
| err |= write_padded(num, len, pad, |
| plr ? ' ' : pch, plr, |
| prfx_hex, vph, arg); |
| } |
| else { |
| err |= write_padded(str_nil, |
| sizeof(str_nil) - 1, |
| pad, ' ', plr, NULL, |
| vph, arg); |
| } |
| break; |
| |
| case 'r': |
| pl = va_arg(ap, const struct pl *); |
| |
| err |= write_padded(pl ? pl->p : NULL, |
| (pl && pl->p) ? pl->l : 0, |
| pad, ' ', plr, NULL, vph, arg); |
| break; |
| |
| case 's': |
| str = va_arg(ap, const char *); |
| err |= write_padded(str, str_len(str), pad, |
| ' ', plr, NULL, vph, arg); |
| break; |
| |
| case 'X': |
| uc = true; |
| /*@fallthrough@*/ |
| case 'x': |
| base = 16; |
| /*@fallthrough@*/ |
| case 'u': |
| switch (lenmod) { |
| |
| case LENMOD_SIZE: |
| n = va_arg(ap, size_t); |
| break; |
| |
| default: |
| case LENMOD_LONG_LONG: |
| n = va_arg(ap, unsigned long long); |
| break; |
| |
| case LENMOD_LONG: |
| n = va_arg(ap, unsigned long); |
| break; |
| |
| case LENMOD_NONE: |
| n = va_arg(ap, unsigned); |
| break; |
| } |
| |
| len = local_itoa(num, n, base, uc); |
| |
| err |= write_padded(num, len, pad, |
| plr ? ' ' : pch, plr, NULL, |
| vph, arg); |
| break; |
| |
| case 'v': |
| str = va_arg(ap, char *); |
| apl = va_arg(ap, va_list *); |
| |
| if (!str || !apl) |
| break; |
| |
| err |= re_vhprintf(str, *apl, vph, arg); |
| break; |
| |
| case 'W': |
| uc = true; |
| /*@fallthrough@*/ |
| case 'w': |
| bptr = va_arg(ap, uint8_t *); |
| len = va_arg(ap, size_t); |
| |
| len = bptr ? len : 0; |
| pch = plr ? ' ' : pch; |
| |
| while (!plr && pad-- > (len * 2)) |
| err |= vph(&pch, 1, arg); |
| |
| for (i=0; i<len; i++) { |
| const uint8_t v = *bptr++; |
| uint32_t l = local_itoa(num, v, 16, uc); |
| err |= write_padded(num, l, 2, '0', |
| false, NULL, vph, arg); |
| } |
| |
| while (plr && pad-- > (len * 2)) |
| err |= vph(&pch, 1, arg); |
| |
| break; |
| |
| case 'z': |
| lenmod = LENMOD_SIZE; |
| fm = true; |
| break; |
| |
| case 'j': |
| sa = va_arg(ap, struct sa *); |
| if (!sa) |
| break; |
| if (sa_ntop(sa, addr, sizeof(addr))) { |
| err |= write_padded("?", 1, pad, ' ', |
| plr, NULL, vph, arg); |
| break; |
| } |
| err |= write_padded(addr, strlen(addr), pad, ' ', |
| plr, NULL, vph, arg); |
| break; |
| |
| |
| case 'J': |
| sa = va_arg(ap, struct sa *); |
| if (!sa) |
| break; |
| if (sa_ntop(sa, addr, sizeof(addr))) { |
| err |= write_padded("?", 1, pad, ' ', |
| plr, NULL, vph, arg); |
| break; |
| } |
| |
| #ifdef HAVE_INET6 |
| if (AF_INET6 == sa_af(sa)) { |
| ch = '['; |
| err |= vph(&ch, 1, arg); |
| } |
| #endif |
| err |= write_padded(addr, strlen(addr), pad, ' ', |
| plr, NULL, vph, arg); |
| #ifdef HAVE_INET6 |
| if (AF_INET6 == sa_af(sa)) { |
| ch = ']'; |
| err |= vph(&ch, 1, arg); |
| } |
| #endif |
| |
| ch = ':'; |
| err |= vph(&ch, 1, arg); |
| len = local_itoa(num, sa_port(sa), 10, false); |
| err |= write_padded(num, len, pad, |
| plr ? ' ' : pch, plr, NULL, |
| vph, arg); |
| |
| break; |
| |
| default: |
| if (('0' <= *p) && (*p <= '9')) { |
| if (!pad && ('0' == *p)) { |
| pch = '0'; |
| } |
| else { |
| pad *= 10; |
| pad += *p - '0'; |
| } |
| fm = true; |
| break; |
| } |
| |
| ch = '?'; |
| |
| err |= vph(&ch, 1, arg); |
| break; |
| } |
| |
| if (!fm) |
| p0 = p + 1; |
| } |
| |
| if (!fm && p > p0) |
| err |= vph(p0, p - p0, arg); |
| |
| return err; |
| } |
| |
| |
| static int print_handler(const char *p, size_t size, void *arg) |
| { |
| struct pl *pl = arg; |
| |
| if (size > pl->l) |
| return ENOMEM; |
| |
| memcpy((void *)pl->p, p, size); |
| |
| pl_advance(pl, size); |
| |
| return 0; |
| } |
| |
| |
| struct dyn_print { |
| char *str; |
| char *p; |
| size_t l; |
| size_t size; |
| }; |
| |
| |
| static int print_handler_dyn(const char *p, size_t size, void *arg) |
| { |
| struct dyn_print *dp = arg; |
| |
| if (size > dp->l - 1) { |
| const size_t new_size = MAX(dp->size + size, dp->size * 2); |
| char *str = mem_realloc(dp->str, new_size); |
| if (!str) |
| return ENOMEM; |
| |
| dp->str = str; |
| dp->l += new_size - dp->size; |
| dp->p = dp->str + new_size - dp->l; |
| dp->size = new_size; |
| } |
| |
| memcpy(dp->p, p, size); |
| |
| dp->p += size; |
| dp->l -= size; |
| |
| return 0; |
| } |
| |
| |
| struct strm_print { |
| FILE *f; |
| size_t n; |
| }; |
| |
| static int print_handler_stream(const char *p, size_t size, void *arg) |
| { |
| struct strm_print *sp = arg; |
| |
| if (1 != fwrite(p, size, 1, sp->f)) |
| return ENOMEM; |
| |
| sp->n += size; |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Print a formatted string to a file stream, using va_list |
| * |
| * @param stream File stream for the output |
| * @param fmt Formatted string |
| * @param ap Variable-arguments list |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_vfprintf(FILE *stream, const char *fmt, va_list ap) |
| { |
| struct strm_print sp; |
| |
| if (!stream) |
| return -1; |
| |
| sp.f = stream; |
| sp.n = 0; |
| |
| if (0 != re_vhprintf(fmt, ap, print_handler_stream, &sp)) |
| return -1; |
| |
| return (int)sp.n; |
| } |
| |
| |
| /** |
| * Print a formatted string to stdout, using va_list |
| * |
| * @param fmt Formatted string |
| * @param ap Variable-arguments list |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_vprintf(const char *fmt, va_list ap) |
| { |
| return re_vfprintf(stdout, fmt, ap); |
| } |
| |
| |
| /** |
| * Print a formatted string to a buffer, using va_list |
| * |
| * @param str Buffer for output string |
| * @param size Size of buffer |
| * @param fmt Formatted string |
| * @param ap Variable-arguments list |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) |
| { |
| struct pl pl; |
| int err; |
| |
| if (!str || !size) |
| return -1; |
| |
| pl.p = str; |
| pl.l = size - 1; |
| |
| err = re_vhprintf(fmt, ap, print_handler, &pl); |
| |
| str[size - pl.l - 1] = '\0'; |
| |
| return err ? -1 : (int)(size - pl.l - 1); |
| } |
| |
| |
| /** |
| * Print a formatted string to a dynamically allocated buffer, using va_list |
| * |
| * @param strp Pointer for output string |
| * @param fmt Formatted string |
| * @param ap Variable-arguments list |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int re_vsdprintf(char **strp, const char *fmt, va_list ap) |
| { |
| struct dyn_print dp; |
| int err; |
| |
| if (!strp) |
| return EINVAL; |
| |
| dp.size = 16; |
| dp.str = mem_alloc(dp.size, NULL); |
| if (!dp.str) |
| return ENOMEM; |
| |
| dp.p = dp.str; |
| dp.l = dp.size; |
| |
| err = re_vhprintf(fmt, ap, print_handler_dyn, &dp); |
| if (err) |
| goto out; |
| |
| *dp.p = '\0'; |
| |
| out: |
| if (err) |
| mem_deref(dp.str); |
| else |
| *strp = dp.str; |
| |
| return err; |
| } |
| |
| |
| /** |
| * Print a formatted string |
| * |
| * @param pf Print backend |
| * @param fmt Formatted string |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int re_hprintf(struct re_printf *pf, const char *fmt, ...) |
| { |
| va_list ap; |
| int err; |
| |
| if (!pf) |
| return EINVAL; |
| |
| va_start(ap, fmt); |
| err = re_vhprintf(fmt, ap, pf->vph, pf->arg); |
| va_end(ap); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Print a formatted string to a file stream |
| * |
| * @param stream File stream for output |
| * @param fmt Formatted string |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_fprintf(FILE *stream, const char *fmt, ...) |
| { |
| va_list ap; |
| int n; |
| |
| va_start(ap, fmt); |
| n = re_vfprintf(stream, fmt, ap); |
| va_end(ap); |
| |
| return n; |
| } |
| |
| |
| /** |
| * Print a formatted string to stdout |
| * |
| * @param fmt Formatted string |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_printf(const char *fmt, ...) |
| { |
| va_list ap; |
| int n; |
| |
| va_start(ap, fmt); |
| n = re_vprintf(fmt, ap); |
| va_end(ap); |
| |
| return n; |
| } |
| |
| |
| /** |
| * Print a formatted string to a buffer |
| * |
| * @param str Buffer for output string |
| * @param size Size of buffer |
| * @param fmt Formatted string |
| * |
| * @return The number of characters printed, or -1 if error |
| */ |
| int re_snprintf(char *str, size_t size, const char *fmt, ...) |
| { |
| va_list ap; |
| int n; |
| |
| va_start(ap, fmt); |
| n = re_vsnprintf(str, size, fmt, ap); |
| va_end(ap); |
| |
| return n; |
| } |
| |
| |
| /** |
| * Print a formatted string to a buffer |
| * |
| * @param strp Buffer pointer for output string |
| * @param fmt Formatted string |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int re_sdprintf(char **strp, const char *fmt, ...) |
| { |
| va_list ap; |
| int err; |
| |
| va_start(ap, fmt); |
| err = re_vsdprintf(strp, fmt, ap); |
| va_end(ap); |
| |
| return err; |
| } |