]> pd.if.org Git - pdclib/blobdiff - draft.c
Disabled format string warnings on ...printf() regtest drivers.
[pdclib] / draft.c
diff --git a/draft.c b/draft.c
index c5f7ad78b5c6eec47108cc2753fa4d65335ee456..570f305353b9352e07b51be7265c5ccb4e981ef7 100644 (file)
--- a/draft.c
+++ b/draft.c
-#include <stdio.h>
-#include <stdlib.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdint.h>
-#include <stdbool.h>
-#include <ctype.h>
-#include <assert.h>
-#include <string.h>
-#include <limits.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_minus   1<<0
-#define E_plus    1<<1
-#define E_alt     1<<2
-#define E_space   1<<3
-#define E_zero    1<<4
-#define E_done    1<<5
-#define E_char    1<<6
-#define E_short   1<<7
-#define E_long    1<<8
-#define E_llong   1<<9
-#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_term    1<<15
-
-void parse_out( const char * spec, va_list ap );
+#define E_minus    1<<0
+#define E_plus     1<<1
+#define E_alt      1<<2
+#define E_space    1<<3
+#define E_zero     1<<4
+#define E_done     1<<5
+#define E_char     1<<6
+#define E_short    1<<7
+#define E_long     1<<8
+#define E_llong    1<<9
+#define E_intmax   1<<10
+#define E_size     1<<11
+#define E_ptrdiff  1<<12
+#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    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                                  */
-    int    prec;  /* precision of current field                              */
+    int           base;  /* base to which the value shall be converted       */
+    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 */
 };
 
-union value_t
+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 )
 {
-      int8_t  int8;
-     uint8_t uint8;
-     int16_t  int16;
-    uint16_t uint16;
-     int32_t  int32;
-    uint32_t uint32;
-     int64_t  int64;
-    uint64_t uint64; 
-};
+    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;
+}
 
-/* x - the character to be delivered
+/* 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 )
 
-/* TODO: Left / right alignment - requires track-keeping of width and printed chars.
-   "Father function", die für right alignment "tail recursive" gerufen wird, und
-   "after" für left alignment? Parameter als struct?
+/* 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 )
             {
@@ -91,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
+        else if ( ! ( status->flags & E_unsigned ) )
         {
+            /* plus sign / extra space are only for unsigned conversions */
             if ( status->flags & E_plus )
             {
                 preface[ preidx++ ] = '+';
@@ -104,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' )
         {
@@ -120,13 +284,23 @@ 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);
             }
         }
+        /* Do the precision padding if necessary. */
+        for ( int i = 0; i < prec_pads; ++i )
+        {
+            DELIVER( '0' );
+        }
+        }
     }
+    /* Recursion tail - print the current digit. */
     {
     int digit = value % status->base;
     if ( digit < 0 )
@@ -135,69 +309,75 @@ static void int2base( intmax_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 ] ) );
+        /* Uppercase letters. Array only used here, only 0-F. */
+        DELIVER( _PDCLIB_Xdigits[ digit ] );
     }
     }
 }
 
-static void padwrap( intmax_t value, struct status_t * status )
+/* 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 )
 {
-    int2base( value, status );
-    if ( status->flags & E_minus )
-    {
-        while ( status->this < status->width )
-        {
-            DELIVER( ' ' );
-            ++(status->this);
-        }
-    }
-    if ( status->i >= status->n )
+    const char * orig_spec = spec;
+    if ( *(++spec) == '%' )
     {
-        status->s[status->n - 1] = '\0';
+        DELIVER( *spec );
+        return ++spec;
     }
-}
+    /* Initializing status structure */
+    status->flags = 0;
+    status->base  = 0;
+    status->this  = 0;
+    status->width = 0;
+    status->prec  = 0;
 
-void parse_out( const char * spec, va_list ap )
-{
-    /* TODO: '%' handled correctly? */
-    struct status_t status = { 0, 0, 0, 0, 0, NULL, 0, EOF };
     /* First come 0..n flags */
-    while ( ! ( status.flags & E_done ) )
+    do
     {
-        switch ( *(++spec) )
+        switch ( *spec )
         {
             case '-':
-                status.flags |= E_minus;
+                status->flags |= E_minus;
+                ++spec;
                 break;
             case '+':
-                status.flags |= E_plus;
+                status->flags |= E_plus;
+                ++spec;
                 break;
             case '#':
-                status.flags |= E_alt;
+                status->flags |= E_alt;
+                ++spec;
                 break;
             case ' ':
-                status.flags |= E_space;
+                status->flags |= E_space;
+                ++spec;
                 break;
             case '0':
-                status.flags |= E_zero;
+                status->flags |= E_zero;
+                ++spec;
                 break;
             default:
-                status.flags |= E_done;
+                status->flags |= E_done;
                 break;
         }
-    }
+    } while ( ! ( status->flags & E_done ) );
+
+    /* Optional field width */
     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;
-            status.width *= -1;
+            status->flags |= E_minus;
+            status->width *= -1;
         }
         ++spec;
     }
@@ -205,30 +385,40 @@ void parse_out( const char * spec, va_list ap )
     {
         /* If a width is given, strtol() will return its value. If not given,
            strtol() will return zero. In both cases, endptr will point to the
-           rest of the conversion specifier.
+           rest of the conversion specifier - just what we need.
         */
-        char * endptr;
-        status.width = (int)strtol( spec, &endptr, 10 );
-        spec = endptr;
+        status->width = (int)strtol( spec, (char**)&spec, 10 );
     }
+
+    /* Optional precision */
     if ( *spec == '.' )
     {
-        if ( *(++spec) == '*' )
+        ++spec;
+        if ( *spec == '*' )
         {
             /* Retrieve precision value from argument stack. A negative value
                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
         {
             char * endptr;
-            status.prec = (int)strtol( spec, &endptr, 10 );
+            status->prec = (int)strtol( spec, &endptr, 10 );
+            if ( spec == endptr )
+            {
+                /* 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;
     }
-    /* We step one character ahead in any case, and step back only if we find
+
+    /* Optional length modifier
+       We step one character ahead in any case, and step back only if we find
        there has been no length modifier (or step ahead another character if it
        has been "hh" or "ll").
     */
@@ -237,58 +427,65 @@ void parse_out( const char * spec, va_list ap )
         case 'h':
             if ( *spec == 'h' )
             {
-                status.flags |= E_char;
+                status->flags |= E_char;
                 ++spec;
             }
             else
             {
-                status.flags |= E_short;
+                status->flags |= E_short;
             }
             break;
         case 'l':
             if ( *spec == 'l' )
             {
-                status.flags |= E_llong;
+                status->flags |= E_llong;
                 ++spec;
             }
             else
             {
-                status.flags |= E_long;
+                status->flags |= E_long;
             }
             break;
         case 'j':
-            status.flags |= E_intmax;
+            status->flags |= E_intmax;
             break;
         case 'z':
-            status.flags |= E_size;
+            status->flags |= E_size;
             break;
         case 't':
-            status.flags |= E_ptrdiff;
+            status->flags |= E_ptrdiff;
             break;
         case 'L':
-            status.flags |= E_double;
+            status->flags |= E_double;
             break;
         default:
-            ++spec;
+            --spec;
             break;
     }
+
+    /* Conversion specifier */
     switch ( *spec )
     {
         case 'd':
+            /* FALLTHROUGH */
         case 'i':
-            /* int2base( 10, value, true ) */
+            status->base = 10;
             break;
         case 'o':
-            /* int2base( 8, value, true ) */
+            status->base = 8;
+            status->flags |= E_unsigned;
             break;
         case 'u':
-            /* uint2base( 10, value, true ) */
+            status->base = 10;
+            status->flags |= E_unsigned;
             break;
         case 'x':
-            /* uint2base( 16, value, true ) */
+            status->base = 16;
+            status->flags |= ( E_lower | E_unsigned );
             break;
         case 'X':
-            /* uint2base( 16, value, false ) */
+            status->base = 16;
+            status->flags |= E_unsigned;
             break;
         case 'f':
         case 'F':
@@ -301,125 +498,190 @@ void parse_out( const char * spec, 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: 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:
-            // undefined
-            return;
+            /* No conversion specifier. Bad conversion. */
+            return orig_spec;
     }
-}
-
-/*
-static void int2base( int value, int base, struct status_t * status )
 
-#define E_minus   1<<0
-#define E_plus    1<<1
-#define E_alt     1<<2
-#define E_space   1<<3
-#define E_zero    1<<4
-#define E_done    1<<5
-#define E_char    1<<6
-#define E_short   1<<7
-#define E_long    1<<8
-#define E_llong   1<<9
-#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
+    /* Do the actual output based on our findings */
+    if ( status->base != 0 )
+    {
+        /* Integer conversions */
+        /* TODO: Check for invalid flag combinations. */
+        if ( status->flags & E_unsigned )
+        {
+            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;
+}
 
-        struct status_t
+inline void test( size_t n, const char * expect, ... )
 {
-    int    flags; 
-    size_t n;     
-    size_t i;     
-    char * s;     
-    size_t width; 
-    size_t prec;  
-};
-*/
+    char * buffer1 = malloc( 50 );
+    char * buffer2 = malloc( 50 );
+    int myrc;
+    int rc;
+    va_list 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 | E_term; \
-    status.n = _n; \
-    status.i = 0; \
-    status.width = _width; \
-    status.prec = _prec; \
-    status.base = _base; \
-    status.this = 0; \
-    memset( status.s, '\0', 50 ); \
-    padwrap( _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;
+}
 
-int main()
+int _PDCLIB_snprintf( char * s, size_t n, const char * format, ... )
 {
-    struct status_t status;
-    int rc;
-    int tmp;
-    char * buffer = malloc( 50 );
-    status.s = malloc( 50 );
-#if 0
-    TESTCASE( E_plus, 5, 0, 0, 1234, 10, "%+d" );
-    TESTCASE( E_space, 3, 0, 0, 1234, 10, "% d" );
-    TESTCASE( E_space, 3, 0, 0, -1234, 10, "% d" );
-    TESTCASE( E_plus, 3, 0, 0, -1234, 10, "%+d" );
-    TESTCASE( E_done, 4, 0, 0, 65535, 16, "%X" );
-    TESTCASE( E_lower | E_alt, 4, 0, 0, 65534, 16, "%#x" );
-    TESTCASE( E_done, 4, 0, 0, 62, 8, "%o" );
-    TESTCASE( E_alt, 4, 0, 0, 62, 8, "%#o" );
-    TESTCASE( E_done, 6, 6, 0, 1234, 10, "%6d" );
-    TESTCASE( E_minus, 6, 6, 0, 1234, 10, "%-6d" );
-    TESTCASE( E_minus, 6, 2, 0, 1234, 10, "%-2d" );
-    TESTCASE( E_done, 6, 2, 0, 1234, 10, "%2d" );
-    TESTCASE( E_zero, 6, 6, 0, -1234, 10, "%06d" );
-    /* TODO: These two are *unsigned* conversions! */
-    TESTCASE( E_zero, 7, 7, 0, -65535, 16, "%07X" );
-    TESTCASE( E_zero, 7, 7, 0, -65535, 10, "%07u" );
+    va_list ap;
+    va_start( ap, format );
+    return _PDCLIB_vsnprintf( s, n, format, ap );
+}
 
-    TESTCASE( E_zero | E_minus, 6, 6, 0, 1234, 10, "%-06d" );
-    TESTCASE( E_plus, 6, 6, 0, 1234, 10, "%+6d" );
-    TESTCASE( E_space, 6, 6, 0, 1234, 10, "% 6d" );
-    TESTCASE( E_space, 6, 6, 0, -1234, 10, "% 6d" );
-    TESTCASE( E_space | E_minus, 6, 6, 0, -1234, 10, "%- 6d" );
-#endif
-    puts( "--- Serious Tests ---\n" );
-    puts( "- Signed min / max -\n" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, CHAR_MIN, 10, "%hhd" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, CHAR_MAX, 10, "%hhd" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, 0, 10, "%hhd" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, SHRT_MIN, 10, "%hd" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, SHRT_MAX, 10, "%hd" );
-    TESTCASE( E_done, 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_done, SIZE_MAX, 0, 0, LONG_MIN, 10, "%ld" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, LONG_MAX, 10, "%ld" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, 0l, 10, "%ld" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, LLONG_MIN, 10, "%lld" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, LLONG_MAX, 10, "%lld" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, 0ll, 10, "%lld" ); 
-    puts( "- Unsigned min / max -\n" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, UCHAR_MAX, 10, "%hhu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, -1, 10, "%hhu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, USHRT_MAX, 10, "%hu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, -1, 10, "%hu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, UINT_MAX, 10, "%u" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, -1, 10, "%u" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, ULONG_MAX, 10, "%lu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, -1l, 10, "%lu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, ULLONG_MAX, 10, "%llu" );
-    TESTCASE( E_done, SIZE_MAX, 0, 0, -1ll, 10, "%llu" );
-    return 0;
+#if 0
+int _PDCLIB_fprintf( FILE * stream, const char * format, va_list ap )
+{
+    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