/* * vsnprintf.c * * vsnprintf(), from which the rest of the printf() * family is built */ #include #include #include #include #include #include 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 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 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 ( o0 ) buffer[n-1] = '\0'; /* Overflow - terminate at end of buffer */ return o; }