#include "pn_printf.h" /* * This implementation was copied from the klibc project. */ #include #include #include #include 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; }