]> pd.if.org Git - pdclib/blobdiff - draft.c
Fixed warnings in test driver.
[pdclib] / draft.c
diff --git a/draft.c b/draft.c
index 8d3e362dddb685d8ca02234c554e6b3e14ebf77b..570f305353b9352e07b51be7265c5ccb4e981ef7 100644 (file)
--- a/draft.c
+++ b/draft.c
@@ -1,16 +1,13 @@
-#include <stdio.h>
-#include <stdlib.h>
 #include <stdarg.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <ctype.h>
-#include <assert.h>
-#include <string.h>
-#include <limits.h>
 #include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
 
+/* These can be removed once integrated into PDCLIB make procedure */
 #undef TEST
 #include </home/solar/src/pdclib/functions/_PDCLIB/digits.c>
+#include </home/solar/src/pdclib/functions/_PDCLIB/Xdigits.c>
 
 /* Using an integer's bits as flags for both the conversion flags and length
    modifiers.
 #define E_intmax   1<<10
 #define E_size     1<<11
 #define E_ptrdiff  1<<12
-#define E_double   1<<13
-#define E_lower    1<<14
-#define E_unsigned 1<<15
+#define E_intptr   1<<13
+#define E_double   1<<14
+#define E_lower    1<<15
+#define E_unsigned 1<<16
 
 struct status_t
 {
     int           base;  /* base to which the value shall be converted       */
-    int_fast16_t  flags; /* flags and length modifiers                       */
+    int_fast32_t  flags; /* flags and length modifiers                       */
     size_t        n;     /* maximum number of characters to be written       */
     size_t        i;     /* number of characters already written             */
     size_t        this;  /* number of output chars in the current conversion */
     char *        s;     /* target buffer                                    */
     size_t        width; /* width of current field                           */
     size_t        prec;  /* precision of current field                       */
+    FILE *        stream;/* for to-stream output                             */
+    va_list       ap;    /* the argument stack passed to the printf function */
 };
 
-/* x - the character to be delivered
+const char * parse_out( const char * spec, struct status_t * status );
+inline void test( size_t n, const char * expect, ... );
+int _PDCLIB_vsnprintf( char * buffer, size_t n, const char * format, va_list ap );
+int _PDCLIB_snprintf( char * s, size_t n, const char * format, ... );
+
+/* The following only for testing. */
+#include <limits.h>
+#include <string.h>
+
+int main( void )
+{
+    test( SIZE_MAX, "%hhd", CHAR_MIN );
+    test( SIZE_MAX, "%hhd", CHAR_MAX );
+    test( SIZE_MAX, "%hhd", 0 );
+    test( SIZE_MAX, "%hd", SHRT_MIN );
+    test( SIZE_MAX, "%hd", SHRT_MAX );
+    test( SIZE_MAX, "%hd", 0 );
+    test( SIZE_MAX, "%d", INT_MIN );
+    test( SIZE_MAX, "%d", INT_MAX );
+    test( SIZE_MAX, "%d", 0 );
+    test( SIZE_MAX, "%ld", LONG_MIN );
+    test( SIZE_MAX, "%ld", LONG_MAX );
+    test( SIZE_MAX, "%ld", 0l );
+    test( SIZE_MAX, "%lld", LLONG_MIN );
+    test( SIZE_MAX, "%lld", LLONG_MAX );
+    test( SIZE_MAX, "%lld", 0ll );
+    test( SIZE_MAX, "%hhu", UCHAR_MAX );
+    test( SIZE_MAX, "%hhu", (unsigned char)-1 );
+    test( SIZE_MAX, "%hu", USHRT_MAX );
+    test( SIZE_MAX, "%hu", (unsigned short)-1 );
+    test( SIZE_MAX, "%u", UINT_MAX );
+    test( SIZE_MAX, "%u", -1u );
+    test( SIZE_MAX, "%lu", ULONG_MAX );
+    test( SIZE_MAX, "%lu", -1ul );
+    test( SIZE_MAX, "%llu", ULLONG_MAX );
+    test( SIZE_MAX, "%llu", -1ull );
+    test( SIZE_MAX, "%X", UINT_MAX );
+    test( SIZE_MAX, "%#X", -1u );
+    test( SIZE_MAX, "%x", UINT_MAX );
+    test( SIZE_MAX, "%#x", -1u );
+    test( SIZE_MAX, "%o", UINT_MAX );
+    test( SIZE_MAX, "%#o", -1u );
+    test( SIZE_MAX, "%.0#o", 0 );
+    test( SIZE_MAX, "%+d", INT_MIN );
+    test( SIZE_MAX, "%+d", INT_MAX );
+    test( SIZE_MAX, "%+d", 0 );
+    test( SIZE_MAX, "%+u", UINT_MAX );
+    test( SIZE_MAX, "%+u", -1u );
+    test( SIZE_MAX, "% d", INT_MIN );
+    test( SIZE_MAX, "% d", INT_MAX );
+    test( SIZE_MAX, "% d", 0 );
+    test( SIZE_MAX, "% u", UINT_MAX );
+    test( SIZE_MAX, "% u", -1u );
+    test( SIZE_MAX, "%9d", INT_MIN );
+    test( SIZE_MAX, "%9d", INT_MAX );
+    test( SIZE_MAX, "%10d", INT_MIN );
+    test( SIZE_MAX, "%10d", INT_MAX );
+    test( SIZE_MAX, "%11d", INT_MIN );
+    test( SIZE_MAX, "%11d", INT_MAX );
+    test( SIZE_MAX, "%12d", INT_MIN );
+    test( SIZE_MAX, "%12d", INT_MAX );
+    test( SIZE_MAX, "%-9d", INT_MIN );
+    test( SIZE_MAX, "%-9d", INT_MAX );
+    test( SIZE_MAX, "%-10d", INT_MIN );
+    test( SIZE_MAX, "%-10d", INT_MAX );
+    test( SIZE_MAX, "%-11d", INT_MIN );
+    test( SIZE_MAX, "%-11d", INT_MAX );
+    test( SIZE_MAX, "%-12d", INT_MIN );
+    test( SIZE_MAX, "%-12d", INT_MAX );
+    test( SIZE_MAX, "%09d", INT_MIN );
+    test( SIZE_MAX, "%09d", INT_MAX );
+    test( SIZE_MAX, "%010d", INT_MIN );
+    test( SIZE_MAX, "%010d", INT_MAX );
+    test( SIZE_MAX, "%011d", INT_MIN );
+    test( SIZE_MAX, "%011d", INT_MAX );
+    test( SIZE_MAX, "%012d", INT_MIN );
+    test( SIZE_MAX, "%012d", INT_MAX );
+    test( SIZE_MAX, "%-09d", INT_MIN );
+    test( SIZE_MAX, "%-09d", INT_MAX );
+    test( SIZE_MAX, "%-010d", INT_MIN );
+    test( SIZE_MAX, "%-010d", INT_MAX );
+    test( SIZE_MAX, "%-011d", INT_MIN );
+    test( SIZE_MAX, "%-011d", INT_MAX );
+    test( SIZE_MAX, "%-012d", INT_MIN );
+    test( SIZE_MAX, "%-012d", INT_MAX );
+    test( 8, "%9d", INT_MAX );
+    test( 8, "%9d", INT_MIN );
+    test( 9, "%9d", INT_MAX );
+    test( 9, "%9d", INT_MIN );
+    test( 10, "%9d", INT_MAX );
+    test( 10, "%9d", INT_MIN );
+    test( 9, "%10d", INT_MAX );
+    test( 9, "%10d", INT_MIN );
+    test( 10, "%10d", INT_MAX );
+    test( 10, "%10d", INT_MIN );
+    test( 11, "%10d", INT_MAX );
+    test( 11, "%10d", INT_MIN );
+    test( 10, "%11d", INT_MAX );
+    test( 10, "%11d", INT_MIN );
+    test( 11, "%11d", INT_MAX );
+    test( 11, "%11d", INT_MIN );
+    test( 12, "%11d", INT_MAX );
+    test( 12, "%11d", INT_MIN );
+    test( 11, "%12d", INT_MAX );
+    test( 11, "%12d", INT_MIN );
+    test( 12, "%12d", INT_MAX );
+    test( 12, "%12d", INT_MIN );
+    test( 13, "%12d", INT_MAX );
+    test( 13, "%12d", INT_MIN );
+    test( SIZE_MAX, "%030.20d", INT_MAX );
+    test( SIZE_MAX, "%.6x", UINT_MAX );
+    test( SIZE_MAX, "%#6.3x", UINT_MAX );
+    test( SIZE_MAX, "%#3.6x", UINT_MAX );
+    test( SIZE_MAX, "%.6d", INT_MIN );
+    test( SIZE_MAX, "%6.3d", INT_MIN );
+    test( SIZE_MAX, "%3.6d", INT_MIN );
+    test( SIZE_MAX, "%#0.6x", UINT_MAX );
+    test( SIZE_MAX, "%#06.3x", UINT_MAX );
+    test( SIZE_MAX, "%#03.6x", UINT_MAX );
+    test( SIZE_MAX, "%#0.6d", INT_MAX );
+    test( SIZE_MAX, "%#06.3d", INT_MAX );
+    test( SIZE_MAX, "%#03.6d", INT_MAX );
+    test( SIZE_MAX, "%#+.6d", INT_MAX );
+    test( SIZE_MAX, "%#+6.3d", INT_MAX );
+    test( SIZE_MAX, "%#+3.6d", INT_MAX );
+    test( SIZE_MAX, "%+0.6d", INT_MAX );
+    test( SIZE_MAX, "%+06.3d", INT_MAX );
+    test( SIZE_MAX, "%+03.6d", INT_MAX );
+    test( SIZE_MAX, "- %d", INT_MAX );
+    test( SIZE_MAX, "- %d %% %d", INT_MAX, INT_MIN );
+    test( SIZE_MAX, "%c", 'x' );
+    test( SIZE_MAX, "%s", "abcdef" );
+    test( SIZE_MAX, "%p", 0xdeadbeef );
+    {
+        char buffer[50];
+        int val1, val2, val3, val4;
+        snprintf( buffer, SIZE_MAX, "123456%n789%n", &val1, &val2 );
+        _PDCLIB_snprintf( buffer, SIZE_MAX, "123456%n789%n", &val3, &val4 );
+        if ( ( val1 != val3 ) || ( val2 != val4 ) )
+        {
+            printf( "Output %d/%d\nExpect %d/%d\n\n", val1, val2, val3, val4 );
+        }
+    }
+    return 0;
+}
+
+/* This macro delivers a given character to either a memory buffer or a stream,
+   depending on the contents of 'status' (struct status_t).
+   x - the character to be delivered
    i - pointer to number of characters already delivered in this call
    n - pointer to maximum number of characters to be delivered in this call
    s - the buffer into which the character shall be delivered
-   TODO: Overruns.
 */
-#define DELIVER( x ) do { if ( status->i < status->n ) status->s[status->i] = x; ++(status->i); } while ( 0 )
+#define DELIVER( x ) do { if ( status->i < status->n ) { if ( status->stream != NULL ) putc( x, status->stream ); else status->s[status->i] = x; } ++(status->i); } while ( 0 )
 
+/* This function recursively converts a given integer value to a given base
+   into a character string. Persistent information - like the number of digits
+   parsed so far - is recorded in a struct status_t, which allows to avoid
+   overwriting snprintf() limits, and enables the function to do the necessary
+   padding / prefixing of the character string eventually printed.
+*/
 static void int2base( intmax_t value, struct status_t * status )
 {
+    /* Registering the character being printed at the end of the function here
+       already so it will be taken into account when the deepestmost recursion
+       does the prefix / padding stuff.
+    */
     ++(status->this);
     if ( ( value / status->base ) != 0 )
     {
+        /* More digits to be done - recurse deeper */
         int2base( value / status->base, status );
     }
     else
     {
-        char preface[3] = "\0\0";
+        /* We reached the last digit, the deepest point of our recursion, and
+           only now know how long the number to be printed actually is. Now we
+           have to do the sign, prefix, width, and precision padding stuff
+           before printing the numbers while we resurface from the recursion.
+        */
+        /* At worst, we need two prefix characters (hex prefix). */
+        char preface[3] = "\0";
         size_t preidx = 0;
         if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
         {
+            /* Octal / hexadecimal prefix for "%#" conversions */
             preface[ preidx++ ] = '0';
             if ( status->base == 16 )
             {
@@ -73,10 +238,12 @@ static void int2base( intmax_t value, struct status_t * status )
         }
         if ( value < 0 )
         {
+            /* Negative sign for negative values - at all times. */
             preface[ preidx++ ] = '-';
         }
         else if ( ! ( status->flags & E_unsigned ) )
         {
+            /* plus sign / extra space are only for unsigned conversions */
             if ( status->flags & E_plus )
             {
                 preface[ preidx++ ] = '+';
@@ -86,14 +253,29 @@ static void int2base( intmax_t value, struct status_t * status )
                 preface[ preidx++ ] = ' ';
             }
         }
-        if ( ! ( ( status->flags & E_minus ) || ( status->flags & E_zero ) ) )
         {
-            while ( ( status->this + preidx ) < status->width )
+        size_t prec_pads = ( status->prec > status->this ) ? ( status->prec - status->this ) : 0;
+        if ( ! ( status->flags & ( E_minus | E_zero ) ) )
+        {
+            /* Space padding is only done if no zero padding or left alignment
+               is requested. Leave space for any prefixes determined above.
+            */
+            /* The number of characters to be printed, plus prefixes if any. */
+            /* This line contained probably the most stupid, time-wasting bug
+               I've ever perpetrated. Greetings to Samface, DevL, and all
+               sceners at Breakpoint 2006.
+            */
+            size_t characters = preidx + ( ( status->this > status->prec ) ? status->this : status->prec );
+            if ( status->width > characters )
             {
-                DELIVER( ' ' );
-                ++(status->this);
+                for ( int i = 0; i < status->width - characters; ++i )
+                {
+                    DELIVER( ' ' );
+                    ++(status->this);
+                }
             }
         }
+        /* Now we did the padding, do the prefixes (if any). */
         preidx = 0;
         while ( preface[ preidx ] != '\0' )
         {
@@ -102,103 +284,24 @@ static void int2base( intmax_t value, struct status_t * status )
         }
         if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
         {
+            /* If field is not left aligned, and zero padding is requested, do
+               so.
+            */
             while ( status->this < status->width )
             {
                 DELIVER( '0' );
                 ++(status->this);
             }
         }
-    }
-    {
-    int digit = value % status->base;
-    if ( digit < 0 )
-    {
-        digit *= -1;
-    }
-    if ( status->flags & E_lower )
-    {
-        DELIVER( _PDCLIB_digits[ digit ] );
-    }
-    else
-    {
-        DELIVER( toupper( _PDCLIB_digits[ digit ] ) );
-    }
-    }
-}
-
-static void padwrap( intmax_t value, struct status_t * status )
-{
-    if ( status->flags & E_char )
-    {
-        value = (char)value;
-    }
-    else if ( status->flags & E_short )
-    {
-        value = (short)value;
-    }
-    else if ( status->flags & E_long )
-    {
-        value = (long)value;
-    }
-    else if ( status->flags & E_llong )
-    {
-        value = (long long)value;
-    }
-    else if ( status->flags & E_ptrdiff )
-    {
-        value = (ptrdiff_t)value;
-    }
-    else if ( ! ( status->flags & E_intmax ) )
-    {
-        value = (int)value;
-    }
-    int2base( value, status );
-    if ( status->flags & E_minus )
-    {
-        while ( status->this < status->width )
+        /* Do the precision padding if necessary. */
+        for ( int i = 0; i < prec_pads; ++i )
         {
-            DELIVER( ' ' );
-            ++(status->this);
+            DELIVER( '0' );
+        }
         }
     }
-    if ( status->i >= status->n )
-    {
-        status->s[status->n - 1] = '\0';
-    }
-}
-
-static void upadwrap( uintmax_t value, struct status_t * status )
-{
-    if ( status->flags & E_char )
-    {
-        value = (unsigned char)value;
-    }
-    else if ( status->flags & E_short )
-    {
-        value = (unsigned short)value;
-    }
-    else if ( status->flags & E_long )
-    {
-        value = (unsigned long)value;
-    }
-    else if ( status->flags & E_llong )
-    {
-        value = (unsigned long long)value;
-    }
-    else if ( status->flags & E_size )
-    {
-        value = (size_t)value;
-    }
-    else
-    {
-        value = (unsigned int)value;
-    }
-    status->flags |= E_unsigned;
-    ++(status->this);
-    if ( ( value / status->base ) != 0 )
+    /* Recursion tail - print the current digit. */
     {
-        int2base( (intmax_t)(value / status->base), status );
-    }
     int digit = value % status->base;
     if ( digit < 0 )
     {
@@ -206,38 +309,34 @@ static void upadwrap( uintmax_t value, struct status_t * status )
     }
     if ( status->flags & E_lower )
     {
+        /* Lowercase letters. Same array used for strto...(). */
         DELIVER( _PDCLIB_digits[ digit ] );
     }
     else
     {
-        DELIVER( toupper( _PDCLIB_digits[ digit ] ) );
-    }
-    if ( status->flags & E_minus )
-    {
-        while ( status->this < status->width )
-        {
-            DELIVER( ' ' );
-            ++(status->this);
-        }
+        /* Uppercase letters. Array only used here, only 0-F. */
+        DELIVER( _PDCLIB_Xdigits[ digit ] );
     }
-    if ( status->i >= status->n )
-    {
-        status->s[status->n - 1] = '\0';
     }
 }
 
-void parse_out( const char * spec, struct status_t * status, va_list ap );
-
-void parse_out( const char * spec, struct status_t * status, va_list ap )
+/* This function is to be called with spec pointing to the leading '%' of a
+   printf() conversion specifier, with ap being 
+*/
+const char * parse_out( const char * spec, struct status_t * status )
 {
-    /* TODO: "%%" handled correctly? */
-
+    const char * orig_spec = spec;
+    if ( *(++spec) == '%' )
+    {
+        DELIVER( *spec );
+        return ++spec;
+    }
     /* Initializing status structure */
     status->flags = 0;
-    status->base = 0;
-    status->this = 0;
+    status->base  = 0;
+    status->this  = 0;
     status->width = 0;
-    status->prec = 0;
+    status->prec  = 0;
 
     /* First come 0..n flags */
     do
@@ -274,7 +373,7 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
     if ( *spec == '*' )
     {
         /* Retrieve width value from argument stack */
-        if ( ( status->width = va_arg( ap, int ) ) < 0 )
+        if ( ( status->width = va_arg( status->ap, int ) ) < 0 )
         {
             /* Negative value is '-' flag plus absolute value */
             status->flags |= E_minus;
@@ -301,7 +400,7 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
                is as if no precision is given - as precision is initalized to
                EOF (negative), there is no need for testing for negative here.
             */
-            status->prec = va_arg( ap, int );
+            status->prec = va_arg( status->ap, int );
         }
         else
         {
@@ -309,9 +408,13 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
             status->prec = (int)strtol( spec, &endptr, 10 );
             if ( spec == endptr )
             {
-                /* TODO: Decimal point but no number - bad conversion specifier. */
+                /* Decimal point but no number - bad conversion specifier. */
+                return orig_spec;
             }
+            spec = endptr;
         }
+        /* Having a precision cancels out any zero flag. */
+        status->flags ^= E_zero;
     }
 
     /* Optional length modifier
@@ -364,11 +467,13 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
     switch ( *spec )
     {
         case 'd':
+            /* FALLTHROUGH */
         case 'i':
             status->base = 10;
             break;
         case 'o':
             status->base = 8;
+            status->flags |= E_unsigned;
             break;
         case 'u':
             status->base = 10;
@@ -379,8 +484,8 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
             status->flags |= ( E_lower | E_unsigned );
             break;
         case 'X':
-            status->flags = E_unsigned;
             status->base = 16;
+            status->flags |= E_unsigned;
             break;
         case 'f':
         case 'F':
@@ -393,197 +498,190 @@ void parse_out( const char * spec, struct status_t * status, va_list ap )
         case 'A':
             break;
         case 'c':
-            break;
+            /* TODO: Flags, wide chars. */
+            DELIVER( va_arg( status->ap, int ) );
+            return ++spec;
         case 's':
-            break;
+            /* TODO: Flags, wide chars. */
+            {
+                char * s = va_arg( status->ap, char * );
+                while ( *s != '\0' )
+                {
+                    DELIVER( *(s++) );
+                }
+                return ++spec;
+            }
         case 'p':
-            /* uint2base( 16, (intptr_t)value, true ) */
-        case 'n':
-        case '%':
-            // conversion specifier
-            /* TODO: May this be accompaigned by flags, width, precision, length modifier at all? */
+            /* TODO: E_long -> E_intptr */
+            status->base = 16;
+            status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
             break;
+        case 'n':
+           {
+               int * val = va_arg( status->ap, int * );
+               *val = status->i;
+               return ++spec;
+           }
         default:
-            /* TODO: No conversion specifier. Bad conversion. */
-            return;
-    }
-    switch ( status->flags )
-    {
-        /* TODO */
+            /* No conversion specifier. Bad conversion. */
+            return orig_spec;
     }
+
+    /* Do the actual output based on our findings */
     if ( status->base != 0 )
     {
         /* Integer conversions */
-        switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_unsigned ) )
+        /* TODO: Check for invalid flag combinations. */
+        if ( status->flags & E_unsigned )
         {
-            case E_char:
-                padwrap( (intmax_t)(char)va_arg( ap, int ), status );
-                break;
-            case E_char | E_unsigned:
-                upadwrap( (uintmax_t)(unsigned char)va_arg( ap, int ), status );
-                break;
-            case E_short:
-                padwrap( (intmax_t)(short)va_arg( ap, int ), status );
-                break;
-            case E_short | E_unsigned:
-                upadwrap( (uintmax_t)(unsigned short)va_arg( ap, int ), status );
-                break;
-            case 0:
-                padwrap( (intmax_t)va_arg( ap, int ), status );
-                break;
-            case E_unsigned:
-                upadwrap( (uintmax_t)va_arg( ap, unsigned int ), status );
-                break;
-            case E_long:
-                padwrap( (intmax_t)va_arg( ap, long ), status );
-                break;
-            case E_long | E_unsigned:
-                upadwrap( (uintmax_t)va_arg( ap, unsigned long ), status );
-                break;
-            case E_llong:
-                padwrap( (intmax_t)va_arg( ap, long long ), status );
-                break;
-            case E_llong | E_unsigned:
-                upadwrap( (uintmax_t)va_arg( ap, unsigned long long ), status );
-                break;
+            uintmax_t value;
+            switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
+            {
+                case E_char:
+                    value = (uintmax_t)(unsigned char)va_arg( status->ap, int );
+                    break;
+                case E_short:
+                    value = (uintmax_t)(unsigned short)va_arg( status->ap, int );
+                    break;
+                case 0:
+                    value = (uintmax_t)va_arg( status->ap, unsigned int );
+                    break;
+                case E_long:
+                    value = (uintmax_t)va_arg( status->ap, unsigned long );
+                    break;
+                case E_llong:
+                    value = (uintmax_t)va_arg( status->ap, unsigned long long );
+                    break;
+                case E_size:
+                    value = (uintmax_t)va_arg( status->ap, size_t );
+                    break;
+            }
+            ++(status->this);
+            if ( ( value / status->base ) != 0 )
+            {
+                int2base( (intmax_t)(value / status->base), status );
+            }
+            int digit = value % status->base;
+            if ( digit < 0 )
+            {
+                digit *= -1;
+            }
+            if ( status->flags & E_lower )
+            {
+                DELIVER( _PDCLIB_digits[ digit ] );
+            }
+            else
+            {
+                DELIVER( _PDCLIB_Xdigits[ digit ] );
+            }
+        }
+        else
+        {
+            switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
+            {
+                case E_char:
+                    int2base( (intmax_t)(char)va_arg( status->ap, int ), status );
+                    break;
+                case E_short:
+                    int2base( (intmax_t)(short)va_arg( status->ap, int ), status );
+                    break;
+                case 0:
+                    int2base( (intmax_t)va_arg( status->ap, int ), status );
+                    break;
+                case E_long:
+                    int2base( (intmax_t)va_arg( status->ap, long ), status );
+                    break;
+                case E_llong:
+                    int2base( (intmax_t)va_arg( status->ap, long long ), status );
+                    break;
+                case E_ptrdiff:
+                    int2base( (intmax_t)va_arg( status->ap, ptrdiff_t ), status );
+                    break;
+                case E_intmax:
+                    int2base( va_arg( status->ap, intmax_t ), status );
+                    break;
+            }
+        }
+        if ( status->flags & E_minus )
+        {
+            while ( status->this < status->width )
+            {
+                DELIVER( ' ' );
+                ++(status->this);
+            }
+        }
+        if ( status->i >= status->n )
+        {
+            status->s[status->n - 1] = '\0';
         }
     }
+    return ++spec;
 }
 
-void parse_out_wrapper( const char * spec, struct status_t * status, ... );
-
-void parse_out_wrapper( const char * spec, struct status_t * status, ... )
+inline void test( size_t n, const char * expect, ... )
 {
+    char * buffer1 = malloc( 50 );
+    char * buffer2 = malloc( 50 );
+    int myrc;
+    int rc;
     va_list ap;
-    va_start( ap, status );
-    parse_out( spec, status, ap );
-    va_end( ap );
+    va_start( ap, expect );
+    myrc = _PDCLIB_vsnprintf( buffer1, n, expect, ap );
+    rc = vsnprintf( buffer2, n, expect, ap );
+    if ( ( strcmp( buffer1, buffer2 ) != 0 ) || ( myrc != rc ) )
+    {
+        printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", buffer1, myrc, buffer2, rc );
+    }
+    free( buffer1 );
+    free( buffer2 );
 }
 
-#define TESTCASE( _flags, _n, _width, _prec, _value, _base, _expect ) \
-    status.flags = _flags; \
-    status.n = _n; \
-    status.i = 0; \
-    status.width = _width; \
-    status.prec = _prec; \
-    status.base = _base; \
-    status.this = 0; \
-    memset( status.s, '\0', 50 ); \
-    padwrap( (intmax_t)_value, &status ); \
-    rc = snprintf( buffer, _n, _expect, _value ); \
-    if ( ( strcmp( status.s, buffer ) != 0 ) || ( status.i != rc ) ) \
-    { \
-        printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", status.s, status.i, buffer, rc ); \
+int _PDCLIB_vsnprintf( char * buffer, size_t n, const char * format, va_list ap )
+{
+    struct status_t status = { 0, 0, n, 0, 0, buffer, 0, 0, NULL, ap };
+    while ( *format != '\0' )
+    {
+        const char * rc;
+        if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status ) ) == format ) )
+        {
+            /* No conversion specifier, print verbatim */
+            buffer[ status.i++ ] = *(format++);
+        }
+        else
+        {
+            /* Continue parsing after conversion specifier */
+            format = rc;
+        }
     }
+    buffer[ status.i ] = '\0';
+    return status.i;
+}
 
-#define UTESTCASE( _flags, _n, _width, _prec, _value, _base, _expect ) \
-    status.flags = _flags; \
-    status.n = _n; \
-    status.i = 0; \
-    status.width = _width; \
-    status.prec = _prec; \
-    status.base = _base; \
-    status.this = 0; \
-    memset( status.s, '\0', 50 ); \
-    upadwrap( (uintmax_t)_value, &status ); \
-    rc = snprintf( buffer, _n, _expect, _value ); \
-    if ( ( strcmp( status.s, buffer ) != 0 ) || ( status.i != rc ) ) \
-    { \
-        printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", status.s, status.i, buffer, rc ); \
-    }
+int _PDCLIB_snprintf( char * s, size_t n, const char * format, ... )
+{
+    va_list ap;
+    va_start( ap, format );
+    return _PDCLIB_vsnprintf( s, n, format, ap );
+}
 
-int main( void )
+#if 0
+int _PDCLIB_fprintf( FILE * stream, const char * format, va_list ap )
 {
-    struct status_t status;
-    int rc;
-    char * buffer = malloc( 50 );
-    status.s = calloc( 50, 1 );
-    status.i = 0;
-    status.n = SIZE_MAX;
-    puts( "- parse_out() -\n" );
-    parse_out_wrapper( "d", &status, 1234 );
-    puts( status.s );
-    puts( "- Signed min / max -\n" );
-    TESTCASE( E_char, SIZE_MAX, 0, 0, CHAR_MIN, 10, "%hhd" );
-    TESTCASE( E_char, SIZE_MAX, 0, 0, CHAR_MAX, 10, "%hhd" );
-    TESTCASE( E_char, SIZE_MAX, 0, 0, 0, 10, "%hhd" );
-    TESTCASE( E_short, SIZE_MAX, 0, 0, SHRT_MIN, 10, "%hd" );
-    TESTCASE( E_short, SIZE_MAX, 0, 0, SHRT_MAX, 10, "%hd" );
-    TESTCASE( E_short, SIZE_MAX, 0, 0, 0, 10, "%hd" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, INT_MIN, 10, "%d" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, INT_MAX, 10, "%d" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, 0, 10, "%d" );
-    TESTCASE( E_long, SIZE_MAX, 0, 0, LONG_MIN, 10, "%ld" );
-    TESTCASE( E_long, SIZE_MAX, 0, 0, LONG_MAX, 10, "%ld" );
-    TESTCASE( E_long, SIZE_MAX, 0, 0, 0l, 10, "%ld" );
-    TESTCASE( E_llong, SIZE_MAX, 0, 0, LLONG_MIN, 10, "%lld" );
-    TESTCASE( E_llong, SIZE_MAX, 0, 0, LLONG_MAX, 10, "%lld" );
-    TESTCASE( E_llong, SIZE_MAX, 0, 0, 0ll, 10, "%lld" ); 
-    puts( "- Unsigned min / max -\n" );
-    UTESTCASE( E_char, SIZE_MAX, 0, 0, UCHAR_MAX, 10, "%hhu" );
-    UTESTCASE( E_char, SIZE_MAX, 0, 0, (unsigned char)-1, 10, "%hhu" );
-    UTESTCASE( E_short, SIZE_MAX, 0, 0, USHRT_MAX, 10, "%hu" );
-    UTESTCASE( E_short, SIZE_MAX, 0, 0, (unsigned short)-1, 10, "%hu" );
-    UTESTCASE( E_done, SIZE_MAX, 0, 0, UINT_MAX, 10, "%u" );
-    UTESTCASE( E_done, SIZE_MAX, 0, 0, -1u, 10, "%u" );
-    UTESTCASE( E_long, SIZE_MAX, 0, 0, ULONG_MAX, 10, "%lu" );
-    UTESTCASE( E_long, SIZE_MAX, 0, 0, -1ul, 10, "%lu" );
-    UTESTCASE( E_llong, SIZE_MAX, 0, 0, ULLONG_MAX, 10, "%llu" );
-    UTESTCASE( E_llong, SIZE_MAX, 0, 0, -1ull, 10, "%llu" );
-    puts( "- Hex and Octal, normal and alternative, upper and lowercase -\n" );
-    UTESTCASE( E_done, SIZE_MAX, 0, 0, UINT_MAX, 16, "%X" );
-    UTESTCASE( E_alt, SIZE_MAX, 0, 0, -1u, 16, "%#X" );
-    UTESTCASE( E_done | E_lower, SIZE_MAX, 0, 0, UINT_MAX, 16, "%x" );
-    UTESTCASE( E_alt | E_lower, SIZE_MAX, 0, 0, -1u, 16, "%#x" );
-    UTESTCASE( E_done, SIZE_MAX, 0, 0, UINT_MAX, 8, "%o" );
-    UTESTCASE( E_alt, SIZE_MAX, 0, 0, -1u, 8, "%#o" );
-    puts( "- Plus flag -\n" );
-    TESTCASE( E_plus, SIZE_MAX, 0, 0, INT_MIN, 10, "%+d" );
-    TESTCASE( E_plus, SIZE_MAX, 0, 0, INT_MAX, 10, "%+d" );
-    TESTCASE( E_plus, SIZE_MAX, 0, 0, 0, 10, "%+d" );
-    UTESTCASE( E_plus, SIZE_MAX, 0, 0, UINT_MAX, 10, "%+u" );
-    UTESTCASE( E_plus, SIZE_MAX, 0, 0, -1u, 10, "%+u" );
-    puts( "- Space flag -\n" );
-    TESTCASE( E_space, SIZE_MAX, 0, 0, INT_MIN, 10, "% d" );
-    TESTCASE( E_space, SIZE_MAX, 0, 0, INT_MAX, 10, "% d" );
-    TESTCASE( E_space, SIZE_MAX, 0, 0, 0, 10, "% d" );
-    UTESTCASE( E_space, SIZE_MAX, 0, 0, UINT_MAX, 10, "% u" );
-    UTESTCASE( E_space, SIZE_MAX, 0, 0, -1u, 10, "% u" );
-    puts( "- Field width -\n" );
-    TESTCASE( E_done, SIZE_MAX, 9, 0, INT_MIN, 10, "%9d" );
-    TESTCASE( E_done, SIZE_MAX, 9, 0, INT_MAX, 10, "%9d" );
-    TESTCASE( E_done, SIZE_MAX, 10, 0, INT_MIN, 10, "%10d" );
-    TESTCASE( E_done, SIZE_MAX, 10, 0, INT_MAX, 10, "%10d" );
-    TESTCASE( E_done, SIZE_MAX, 11, 0, INT_MIN, 10, "%11d" );
-    TESTCASE( E_done, SIZE_MAX, 11, 0, INT_MAX, 10, "%11d" );
-    TESTCASE( E_done, SIZE_MAX, 12, 0, INT_MIN, 10, "%12d" );
-    TESTCASE( E_done, SIZE_MAX, 12, 0, INT_MAX, 10, "%12d" );
-    puts( "- Field width (left bound) -\n" );
-    TESTCASE( E_minus, SIZE_MAX, 9, 0, INT_MIN, 10, "%-9d" );
-    TESTCASE( E_minus, SIZE_MAX, 9, 0, INT_MAX, 10, "%-9d" );
-    TESTCASE( E_minus, SIZE_MAX, 10, 0, INT_MIN, 10, "%-10d" );
-    TESTCASE( E_minus, SIZE_MAX, 10, 0, INT_MAX, 10, "%-10d" );
-    TESTCASE( E_minus, SIZE_MAX, 11, 0, INT_MIN, 10, "%-11d" );
-    TESTCASE( E_minus, SIZE_MAX, 11, 0, INT_MAX, 10, "%-11d" );
-    TESTCASE( E_minus, SIZE_MAX, 12, 0, INT_MIN, 10, "%-12d" );
-    TESTCASE( E_minus, SIZE_MAX, 12, 0, INT_MAX, 10, "%-12d" );
-    puts( "- Field width, zero padding -\n");
-    TESTCASE( E_done | E_zero, SIZE_MAX, 9, 0, INT_MIN, 10, "%09d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 9, 0, INT_MAX, 10, "%09d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 10, 0, INT_MIN, 10, "%010d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 10, 0, INT_MAX, 10, "%010d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 11, 0, INT_MIN, 10, "%011d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 11, 0, INT_MAX, 10, "%011d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 12, 0, INT_MIN, 10, "%012d" );
-    TESTCASE( E_done | E_zero, SIZE_MAX, 12, 0, INT_MAX, 10, "%012d" );
-    puts( "- Field width, zero padding (left bound) -\n" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 9, 0, INT_MIN, 10, "%-09d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 9, 0, INT_MAX, 10, "%-09d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 10, 0, INT_MIN, 10, "%-010d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 10, 0, INT_MAX, 10, "%-010d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 11, 0, INT_MIN, 10, "%-011d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 11, 0, INT_MAX, 10, "%-011d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 12, 0, INT_MIN, 10, "%-012d" );
-    TESTCASE( E_minus | E_zero, SIZE_MAX, 12, 0, INT_MAX, 10, "%-012d" );
-    return 0;
+    struct status_t status = { 0, 0, SIZE_MAX, 0, 0, NULL, 0, 0, stream, ap };
+    while ( *format != '\0' )
+    {
+        const char * rc;
+        if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status, ap ) ) == format ) )
+        {
+            /* No conversion specifier, print verbatim */
+            putc( *(format++), stream );
+        }
+        else
+        {
+            /* Continue parsing after conversion specifier */
+            format = rc;
+        }
+    }
+    return status.i;
 }
+#endif