diff options
author | Felipe Contreras <felipe.contreras@gmail.com> | 2010-06-13 20:55:03 +0300 |
---|---|---|
committer | Felipe Contreras <felipe.contreras@gmail.com> | 2010-06-14 02:47:15 +0300 |
commit | ce8fed33ea953903b3064b70541a3ab89c02f47b (patch) | |
tree | 9502fc11a7e1938afc42f84b941b0496af4a65e5 | |
parent | 114d24899162d5220b19545070de0a28be223092 (diff) |
Add pn_printf
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | pn_printf.c | 484 | ||||
-rw-r--r-- | pn_printf.h | 9 |
3 files changed, 494 insertions, 1 deletions
@@ -18,7 +18,7 @@ QUIET_CLEAN = @echo ' CLEAN '$@; endif test: test.o pn_core.o pn_session.o pn_node.o \ - pn_log.o + pn_log.o pn_printf.o test: override CFLAGS += $(GIO_CFLAGS) test: override LIBS += $(GIO_LIBS) diff --git a/pn_printf.c b/pn_printf.c new file mode 100644 index 0000000..a4504f4 --- /dev/null +++ b/pn_printf.c @@ -0,0 +1,484 @@ +#include "pn_printf.h" + +/* + * This implementation was copied from the klibc project. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> + +enum flags { + FL_ZERO = 0x01, + FL_MINUS = 0x02, + FL_PLUS = 0x04, + FL_TICK = 0x08, + FL_SPACE = 0x10, + FL_HASH = 0x20, + FL_SIGNED = 0x40, + FL_UPPER = 0x80, +}; + +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; +} + +static int +pn_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 */ + + /* Next state will be normal */ + state = st_normal; + + /* Canonicalize rank */ + if (rank < MIN_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 */ + val = (uintmax_t) (uintptr_t) va_arg(ap, void *); + if (!val) { + sarg = "(nil)"; + slen = strlen(sarg); + goto is_string; + } + + base = 16; + /* I like this better, but it's not comformant */ + /* prec = (CHAR_BIT * sizeof(void *) + 3) / 4; */ + flags |= FL_HASH; + 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; + + default: + 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; +} + +static int +pn_vasprintf(char **bufp, + const char *format, + va_list ap) +{ + va_list ap1; + int bytes; + char *p; + + va_copy(ap1, ap); + + bytes = pn_vsnprintf(NULL, 0, format, ap1) + 1; + va_end(ap1); + + *bufp = p = malloc(bytes); + if (!p) + return -1; + + return pn_vsnprintf(p, bytes, format, ap); +} + +char * +pn_strdup_vprintf(const char *format, + va_list args) +{ + char *buffer; + pn_vasprintf(&buffer, format, args); + return buffer; +} + +char * +pn_strdup_printf(const char *format, + ...) +{ + char *buffer; + va_list args; + + va_start(args, format); + buffer = pn_strdup_vprintf(format, args); + va_end(args); + + return buffer; +} diff --git a/pn_printf.h b/pn_printf.h new file mode 100644 index 0000000..452e62a --- /dev/null +++ b/pn_printf.h @@ -0,0 +1,9 @@ +#ifndef PN_PRINTF_H +#define PN_PRINTF_H + +#include <stdarg.h> + +char *pn_strdup_vprintf(const char *format, va_list args); +char *pn_strdup_printf(const char *format, ...); + +#endif /* PN_PRINTF_H */ |