| /* |
| * vsnprintf.c |
| * |
| * vsnprintf(), from which the rest of the printf() |
| * family is built |
| */ |
| |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <inttypes.h> |
| #include <string.h> |
| #include <limits.h> |
| |
| enum flags { |
| FL_ZERO = 0x01, /* Zero modifier */ |
| FL_MINUS = 0x02, /* Minus modifier */ |
| FL_PLUS = 0x04, /* Plus modifier */ |
| FL_TICK = 0x08, /* ' modifier */ |
| FL_SPACE = 0x10, /* Space modifier */ |
| FL_HASH = 0x20, /* # modifier */ |
| FL_SIGNED = 0x40, /* Number is signed */ |
| FL_UPPER = 0x80 /* Upper case digits */ |
| }; |
| |
| /* These may have to be adjusted on certain implementations */ |
| 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; |
| } |
| |
| |
| int 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 */ |
| state = st_normal; /* Next state will be normal */ |
| if ( rank < MIN_RANK ) /* Canonicalize 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 */ |
| base = 16; |
| prec = (CHAR_BIT*sizeof(void *)+3)/4; |
| flags |= FL_HASH; |
| val = (uintmax_t)(uintptr_t)va_arg(ap, void *); |
| 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; |
| |
| case 'n': /* Output the number of characters written */ |
| { |
| switch (rank) { |
| case rank_char: |
| *va_arg(ap, signed char *) = o; |
| break; |
| case rank_short: |
| *va_arg(ap, signed short *) = o; |
| break; |
| case rank_int: |
| *va_arg(ap, signed int *) = o; |
| break; |
| case rank_long: |
| *va_arg(ap, signed long *) = o; |
| break; |
| case rank_longlong: |
| *va_arg(ap, signed long long *) = o; |
| break; |
| } |
| } |
| break; |
| |
| default: /* Anything else, including % */ |
| 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; |
| } |