I've been writing my own implementation of stdio.h whenever I get a few spare minutes recently. Nothing fancy or thorough, and without wide character support, it's hardly conforming. But I was looking at one of my intermediate versions of printf and could only marvel at how ugly it was. So much that I'm going to share. :)
Have a peek (and keep in mind that I have no intention of keeping it. ugh):
#include "format_string.h"
#include "jstdio.h"
#include "numeric.h"
#include "print_stream.h"
#include "xtype.h"
static int pad_buffer ( J_FILE_T *out, char buffer[], const char *prefix,
struct print_convspec *spec, j_size_t *nwrit )
{
char *p = buffer;
int width = spec->field_width - j_str_len ( prefix );
int prec = spec->precision;
if ( spec->flags & LEFT_JUSTIFY ) {
if ( spec->flags & SHOW_SIGN && buffer[0] != '-' ) {
if ( _put ( '+', out, *nwrit ) == J_EOF )
return 0;
}
while ( *prefix != '\0' ) {
if ( _put ( *prefix++, out, *nwrit ) == J_EOF )
return 0;
}
while ( *p != '\0' && --prec > 0 ) {
if ( _put ( *p++, out, *nwrit ) == J_EOF )
return 0;
--width;
}
while ( width-- > 0 ) {
if ( _put ( ' ', out, *nwrit ) == J_EOF )
return 0;
}
}
else {
int show_sign = ( spec->flags & SHOW_SIGN ) != 0;
int len = width - j_str_len ( buffer ) - show_sign;
/* We want the sign before leading zeros */
if ( spec->pad != ' ' && show_sign && buffer[0] != '-' ) {
if ( _put ( '+', out, *nwrit ) == J_EOF )
return 0;
}
if ( spec->pad != ' ' && spec->flags & ALT_FORMAT ) {
while ( *prefix != '\0' ) {
if ( _put ( *prefix++, out, *nwrit ) == J_EOF )
return 0;
}
}
while ( len-- > 0 ) {
if ( _put ( spec->pad, out, *nwrit ) == J_EOF )
return 0;
}
/* We want the sign after leading psaces */
if ( spec->pad == ' ' && show_sign && buffer[0] != '-' ) {
if ( _put ( '+', out, *nwrit ) == J_EOF )
return 0;
}
if ( spec->pad == ' ' && spec->flags & ALT_FORMAT ) {
while ( *prefix != '\0' ) {
if ( _put ( *prefix++, out, *nwrit ) == J_EOF )
return 0;
}
}
while ( *p != '\0' && --prec > 0 ) {
if ( _put ( *p++, out, *nwrit ) == J_EOF )
return 0;
}
}
return 1;
}
static char *fp_cvt ( char buffer[], double val, int flag, int prec, int alt )
{
int show_trailing_zeros = 1;
int show_trailing_radix = 0;
int show_float = 0;
if ( prec == -1 )
prec = 6;
switch ( flag ) {
case FMT_NO_FORMAT:
/* %g specifier */
show_float = 1;
if ( alt )
show_trailing_radix = 1;
else
show_trailing_zeros = 0;
break;
default:
/* Assume %e or %f */
if ( prec == 0 && alt )
show_trailing_radix = 1;
break;
}
if ( flag == FMT_FLOAT )
fp_tostring ( buffer, val, prec, '.', show_trailing_zeros, show_trailing_radix, show_float );
else
fp_normalize ( buffer, val, prec, '.', 'e', show_trailing_zeros, show_trailing_radix, show_float );
return buffer;
}
int _print_stream ( J_FILE_T *out, const char *fmt, j_va_list args )
{
struct print_convspec spec = {0};
j_size_t nwrit = 0;
int err = 0;
while ( *fmt != '\0' ) {
/* Process a conversion specifier */
if ( *fmt == '%' ) {
j_size_t count = _get_print_specifier ( &spec, fmt );
if ( count == 0 ) {
/* The conversion failed, process the error state */
goto process_state;
}
/* Jump past the conversion specifier */
fmt += count;
switch ( spec.type ) {
case MOD_STRING:
if ( !pad_buffer ( out,
j_va_arg ( args, char * ),
"", &spec, &nwrit ) )
{
err = 1;
goto process_state;
}
break;
case MOD_CHAR:
if ( _put ( j_va_arg ( args, unsigned char ), out, nwrit ) == J_EOF ) {
err = 1;
goto process_state;
}
break;
case MOD_POINTER:
case MOD_USHORT:
case MOD_UINT:
case MOD_ULONG:
spec.flags &= ~SHOW_SIGN;
case MOD_SHORT:
case MOD_INT:
case MOD_LONG:
{
const char *prefix = "";
long long value;
if ( spec.flags & ALT_FORMAT && spec.format == FMT_OCTAL )
prefix = "0";
else if ( spec.flags & ALT_FORMAT && spec.format == FMT_HEX )
prefix = "0x";
switch ( spec.type ) {
case MOD_SHORT:
value = j_va_arg ( args, short );
break;
case MOD_INT:
value = j_va_arg ( args, int );
break;
case MOD_LONG:
value = j_va_arg ( args, long );
break;
case MOD_USHORT:
value = j_va_arg ( args, unsigned short );
break;
case MOD_UINT:
case MOD_POINTER:
value = j_va_arg ( args, unsigned );
break;
case MOD_ULONG:
value = j_va_arg ( args, unsigned long );
break;
}
if ( !pad_buffer ( out,
dtoa_nobuf ( value, spec.format, 0 ),
prefix, &spec, &nwrit ) )
{
err = 1;
goto process_state;
}
}
break;
case MOD_FLOAT:
case MOD_DOUBLE:
case MOD_LDOUBLE:
{
char buffer[JBUFSIZ];
fp_cvt ( buffer, j_va_arg ( args, double ),
spec.format, spec.precision, spec.flags & ALT_FORMAT );
if ( !pad_buffer ( out, buffer, "", &spec, &nwrit ) ) {
err = 1;
goto process_state;
}
}
break;
case MOD_COUNT:
*j_va_arg ( args, int* ) = nwrit;
break;
case MOD_LITERAL:
if ( _put ( '%', out, nwrit ) == J_EOF ) {
err = 1;
goto process_state;
}
break;
}
}
else {
if ( _put ( *fmt++, out, nwrit ) == J_EOF ) {
err = 1;
goto process_state;
}
}
}
process_state:
return !err ? nwrit : -1;
}
I can only imagine how many bugs are hidden in that mess. The lesson to learn is that writing code without a plan results in ugly code, and adding to ugly code only makes it worse.
Cheers!