/* * This code was originally part of klibc-0.103. * It was adapted by David Hovemeyer * for use in GeekOS (http://geekos.sourceforge.net). * * For information/source for klibc, visit * http://www.kernel.org/pub/linux/libs/klibc/ * http://www.zytor.com/mailman/listinfo/klibc/ * http://www.zytor.com/cvsweb.cgi/klibc/ * * Modifications are marked with "DHH". * Summary of modifications: * * 1. Use struct Output_Sink to emit formatted output, rather than a * character buffer, to allow output polymorphism. * * 2. Buffer generated numbers so that all output is generated in order. * * 3. Don't use long long types: unsigned long is largest * supported type. Arithmetic on 64 bit types requires runtime support * (at least on x86). * * See the file LICENSE-klibc for license information. */ /* * vsnprintf.c * * vsnprintf(), from which the rest of the printf() * family is built */ #include #include #include #include #include /* DHH: for struct Output_Sink */ /* * DHH: Hack, long long arithmetic requires runtime support. * Use unsigned long as greatest unsigned integer supported. */ typedef long intmax_t; typedef unsigned long uintmax_t; typedef unsigned long uintptr_t; /* DHH */ #define ASSERT(exp) \ do { if (!(exp)) while(1); } while (0) 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, #if 0 rank_longlong = 2, #endif }; #define MIN_RANK rank_char #define MAX_RANK rank_long #define INTMAX_RANK rank_long #define SIZE_T_RANK rank_long #define PTRDIFF_T_RANK rank_long /* DHH */ #define EMIT(x) do { (q)->Emit((q), (x)); } while (0) /* * DHH - As a hack, we buffer this many digits when generating * a number. Because this code originally was an implementation * of vnsprintf(), it generated some digits backwards in a buffer. * Obviously we can't do this when emitting output directly * to the console or a file. So, we buffer the generated digits * and then emit them in order. * * This value should be adequate to emit values up to 2^32-1 in * bases 2 or greater, including tick marks. */ #define NDIGITS_MAX 43 static size_t format_int(struct Output_Sink *q, 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; char digit_buffer[NDIGITS_MAX]; /* DHH */ size_t ndigits_save; /* DHH */ /* 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. */ ASSERT(ndigits <= NDIGITS_MAX); /* DHH */ ndigits_save = ndigits; qq = digit_buffer + ndigits; oo = o; /* Emit digits to temp buffer */ b4tick = tickskip; while ( ndigits > 0 ) { if ( !b4tick-- ) { qq--; oo--; ndigits--; *qq = '_'; b4tick = tickskip-1; } qq--; oo--; ndigits--; *qq = digits[val%base]; val /= base; } /* Copy digits to Output_Sink */ for (oo = 0; oo < ndigits_save; ++oo) EMIT(digit_buffer[oo]); /* Emit late space padding */ while ( (flags & FL_MINUS) && width > nchars ) { EMIT(' '); width--; } return o; } /* * DHH - This function was originally vsnprintf(). * I renamed it to Format_Output() and changed it to take * a struct Output_Sink instead of a buffer. That way, it can * be used for any kind of formatted output (string, console, * file, etc.) */ int Format_Output(struct Output_Sink *q, const char *format, va_list ap) { const char *p = format; char ch; 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; #if 0 case rank_longlong: val = (uintmax_t)(intmax_t)va_arg(ap, signed long long); break; #endif } 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; #if 0 case rank_longlong: val = (uintmax_t)va_arg(ap, unsigned long long); break; #endif } /* fall through */ is_integer: sz = format_int(q, 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; #if 0 case rank_longlong: *va_arg(ap, signed long long *) = o; break; #endif } } break; default: /* Anything else, including % */ EMIT(ch); break; } } } } /* Null-terminate the string */ #if 0 if ( o0 ) buffer[n-1] = '\0'; /* Overflow - terminate at end of buffer */ #endif q->Finish(q); return o; }