]> pd.if.org Git - pdclib.old/blobdiff - functions/stdio/_PDCLIB_print.c
Rename all files to match their primary symbol (avoids file conflicts in library...
[pdclib.old] / functions / stdio / _PDCLIB_print.c
diff --git a/functions/stdio/_PDCLIB_print.c b/functions/stdio/_PDCLIB_print.c
new file mode 100644 (file)
index 0000000..95fbe9c
--- /dev/null
@@ -0,0 +1,559 @@
+/* $Id$ */
+
+/* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )
+
+   This file is part of the Public Domain C Library (PDCLib).
+   Permission is granted to use, modify, and / or redistribute at will.
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#ifndef REGTEST
+
+/* Using an integer's bits as flags for both the conversion flags and length
+   modifiers.
+*/
+/* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
+          width flags) into a combined field.
+*/
+#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_ldouble  (1<<14)
+#define E_lower    (1<<15)
+#define E_unsigned (1<<16)
+
+/* This macro delivers a given character to either a memory buffer or a stream,
+   depending on the contents of 'status' (struct _PDCLIB_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
+*/
+#define PUT( x ) \
+do { \
+    int character = x; \
+    if ( status->i < status->n ) { \
+        if ( status->stream != NULL ) \
+            putc_unlocked( character, status->stream ); \
+        else \
+            status->s[status->i] = character; \
+    } \
+    ++(status->i); \
+} while ( 0 )
+
+/* Maximum number of output characters = 
+ *   number of bits in (u)intmax_t / number of bits per character in smallest 
+ *   base. Smallest base is octal, 3 bits/char.
+ *
+ * Additionally require 2 extra characters for prefixes
+ */
+static const size_t maxIntLen = sizeof(intmax_t) * CHAR_BIT / 3 + 1;
+
+static void int2base( uintmax_t value, struct _PDCLIB_status_t * status )
+{
+    char sign = 0;
+    if ( ! ( status->flags & E_unsigned ) ) 
+    {
+        intmax_t signval = (intmax_t) value;
+        bool negative = signval < 0;
+        value = signval < 0 ? -signval : signval;
+
+        if ( negative ) 
+        {
+            sign = '-';
+        } 
+        else if ( status->flags & E_plus ) 
+        {
+            sign = '+';
+        }
+        else if (status->flags & E_space )
+        {
+            sign = ' ';
+        }
+    }
+
+    // The user could theoretically ask for a silly buffer length here. 
+    // Perhaps after a certain size we should malloc? Or do we refuse to protect
+    // them from their own stupidity?
+    size_t bufLen = (status->width > maxIntLen ? status->width : maxIntLen) + 2;
+    char outbuf[bufLen];
+    char * outend = outbuf + bufLen;
+    int written = 0;
+
+    // Build up our output string - backwards
+    {
+        const char * digits = (status->flags & E_lower) ? 
+                                _PDCLIB_digits : _PDCLIB_Xdigits;
+        uintmax_t remaining = value;
+        if(status->prec != 0 || remaining != 0) do {
+            uintmax_t digit = remaining % status->base;
+            remaining /= status->base;
+
+            outend[-++written] = digits[digit];
+        } while(remaining != 0);
+    }
+
+    // Pad field out to the precision specification
+    while( (long) written < status->prec ) outend[-++written] = '0';
+
+    // If a field width specified, and zero padding was requested, then pad to
+    // the field width
+    unsigned padding = 0;
+    if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )    
+    {
+        while( written < (int) status->width ) 
+        {
+            outend[-++written] = '0';
+            padding++;
+        }
+    }
+
+    // Prefixes
+    if ( sign != 0 )
+    {
+        if ( padding == 0 ) written++;
+        outend[-written] = sign;
+    }
+    else if ( status->flags & E_alt )
+    {
+        switch ( status->base )
+        {
+            case 8:
+                if ( outend[-written] != '0' ) outend[-++written] = '0';
+                break;
+            case 16:
+                // No prefix if zero
+                if ( value == 0 ) break;
+
+                written += padding < 2 ? 2 - padding : 0;
+                outend[-written    ] = '0';
+                outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';
+                break;
+            default:
+                break;
+        }
+    }
+
+    // Space padding to field width
+    if ( ! ( status->flags & ( E_minus | E_zero ) ) )
+    {
+        while( written < (int) status->width ) outend[-++written] = ' ';
+    }
+
+    // Write output
+    status->current = written;
+    while ( written )
+        PUT( outend[-written--] );
+}
+
+static void printstr( const char * str, struct _PDCLIB_status_t * status )
+{
+    if ( status->width == 0 || status->flags & E_minus )
+    {
+        // Simple case or left justification
+        while ( str[status->current] && 
+            ( status->prec < 0 || (long)status->current < status->prec ) )
+        {
+            PUT( str[status->current++] );
+        }
+
+        while( status->current < status->width ) 
+        {
+            PUT( ' ' );
+            status->current++;
+        }
+    } else {
+        // Right justification
+        size_t len = status->prec >= 0 ? strnlen( str, status->prec ) 
+                                       :  strlen( str );
+        int padding = status->width - len;
+        while((long)status->current < padding)
+        {
+            PUT( ' ' );
+            status->current++;
+        }
+
+        for( size_t i = 0; i != len; i++ )
+        {
+            PUT( str[i] );
+            status->current++;
+        }
+    }
+}
+
+static void printchar( char chr, struct _PDCLIB_status_t * status )
+{
+    if( ! ( status->flags & E_minus ) )
+    {
+        // Right justification
+        for( ; status->current + 1 < status->width; status->current++)
+        {
+            PUT( ' ' );
+        }
+        PUT( chr );
+        status->current++;
+    } else {
+        // Left justification
+        PUT( chr );
+        status->current++;
+
+        for( ; status->current < status->width; status->current++)
+        {
+            PUT( ' ' );
+        }
+    }
+}
+
+const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
+{
+    const char * orig_spec = spec;
+    if ( *(++spec) == '%' )
+    {
+        /* %% -> print single '%' */
+        PUT( *spec );
+        return ++spec;
+    }
+    /* Initializing status structure */
+    status->flags = 0;
+    status->base  = 0;
+    status->current  = 0;
+    status->width = 0;
+    status->prec  = EOF;
+
+    /* First come 0..n flags */
+    do
+    {
+        switch ( *spec )
+        {
+            case '-':
+                /* left-aligned output */
+                status->flags |= E_minus;
+                ++spec;
+                break;
+            case '+':
+                /* positive numbers prefixed with '+' */
+                status->flags |= E_plus;
+                ++spec;
+                break;
+            case '#':
+                /* alternative format (leading 0x for hex, 0 for octal) */
+                status->flags |= E_alt;
+                ++spec;
+                break;
+            case ' ':
+                /* positive numbers prefixed with ' ' */
+                status->flags |= E_space;
+                ++spec;
+                break;
+            case '0':
+                /* right-aligned padding done with '0' instead of ' ' */
+                status->flags |= E_zero;
+                ++spec;
+                break;
+            default:
+                /* not a flag, exit flag parsing */
+                status->flags |= E_done;
+                break;
+        }
+    } while ( ! ( status->flags & E_done ) );
+
+    /* Optional field width */
+    if ( *spec == '*' )
+    {
+        /* Retrieve width value from argument stack */
+        int width = va_arg( status->arg, int );
+        if ( width < 0 )
+        {
+            status->flags |= E_minus;
+            status->width = abs( width );
+        }
+        else
+        {
+            status->width = width;
+        }
+        ++spec;
+    }
+    else
+    {
+        /* 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 - just what we need.
+        */
+        status->width = (int)strtol( spec, (char**)&spec, 10 );
+    }
+
+    /* Optional precision */
+    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( status->arg, int );
+            ++spec;
+        }
+        else
+        {
+            status->prec = (int)strtol( spec, (char**) &spec, 10 );
+        }
+        /* Having a precision cancels out any zero flag. */
+        status->flags &= ~E_zero;
+    }
+
+    /* 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").
+    */
+    switch ( *(spec++) )
+    {
+        case 'h':
+            if ( *spec == 'h' )
+            {
+                /* hh -> char */
+                status->flags |= E_char;
+                ++spec;
+            }
+            else
+            {
+                /* h -> short */
+                status->flags |= E_short;
+            }
+            break;
+        case 'l':
+            if ( *spec == 'l' )
+            {
+                /* ll -> long long */
+                status->flags |= E_llong;
+                ++spec;
+            }
+            else
+            {
+                /* k -> long */
+                status->flags |= E_long;
+            }
+            break;
+        case 'j':
+            /* j -> intmax_t, which might or might not be long long */
+            status->flags |= E_intmax;
+            break;
+        case 'z':
+            /* z -> size_t, which might or might not be unsigned int */
+            status->flags |= E_size;
+            break;
+        case 't':
+            /* t -> ptrdiff_t, which might or might not be long */
+            status->flags |= E_ptrdiff;
+            break;
+        case 'L':
+            /* L -> long double */
+            status->flags |= E_ldouble;
+            break;
+        default:
+            --spec;
+            break;
+    }
+
+    /* Conversion specifier */
+    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;
+            status->flags |= E_unsigned;
+            break;
+        case 'x':
+            status->base = 16;
+            status->flags |= ( E_lower | E_unsigned );
+            break;
+        case 'X':
+            status->base = 16;
+            status->flags |= E_unsigned;
+            break;
+        case 'f':
+        case 'F':
+        case 'e':
+        case 'E':
+        case 'g':
+        case 'G':
+            break;
+        case 'a':
+        case 'A':
+            break;
+        case 'c':
+            /* TODO: wide chars. */
+            printchar( va_arg( status->arg, int ), status );
+            return ++spec;
+        case 's':
+            /* TODO: wide chars. */
+            {
+                char * s = va_arg( status->arg, char * );
+                printstr( s, status );
+                return ++spec;
+            }
+        case 'p':
+            /* 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->arg, int * );
+               *val = status->i;
+               return ++spec;
+           }
+        default:
+            /* No conversion specifier. Bad conversion. */
+            return orig_spec;
+    }
+
+    /* 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->arg, int );
+                    break;
+                case E_short:
+                    value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
+                    break;
+                case 0:
+                    value = (uintmax_t)va_arg( status->arg, unsigned int );
+                    break;
+                case E_long:
+                    value = (uintmax_t)va_arg( status->arg, unsigned long );
+                    break;
+                case E_llong:
+                    value = (uintmax_t)va_arg( status->arg, unsigned long long );
+                    break;
+                case E_size:
+                    value = (uintmax_t)va_arg( status->arg, size_t );
+                    break;
+            }
+            int2base( value, status );
+        }
+        else
+        {
+            switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
+            {
+                case E_char:
+                    int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
+                    break;
+                case E_short:
+                    int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
+                    break;
+                case 0:
+                    int2base( (intmax_t)va_arg( status->arg, int ), status );
+                    break;
+                case E_long:
+                    int2base( (intmax_t)va_arg( status->arg, long ), status );
+                    break;
+                case E_llong:
+                    int2base( (intmax_t)va_arg( status->arg, long long ), status );
+                    break;
+                case E_ptrdiff:
+                    int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
+                    break;
+                case E_intmax:
+                    int2base( va_arg( status->arg, intmax_t ), status );
+                    break;
+            }
+        }
+        if ( status->flags & E_minus )
+        {
+            while ( status->current < status->width )
+            {
+                PUT( ' ' );
+                ++(status->current);
+            }
+        }
+        if ( status->i >= status->n && status->n > 0 )
+        {
+            status->s[status->n - 1] = '\0';
+        }
+    }
+    return ++spec;
+}
+
+#endif
+
+#ifdef TEST
+#define _PDCLIB_FILEID "_PDCLIB/print.c"
+#define _PDCLIB_STRINGIO
+
+#include <_PDCLIB_test.h>
+
+#ifndef REGTEST
+static int testprintf( char * buffer, const char * format, ... )
+{
+    /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
+    struct _PDCLIB_status_t status;
+    status.base = 0;
+    status.flags = 0;
+    status.n = 100;
+    status.i = 0;
+    status.current = 0;
+    status.s = buffer;
+    status.width = 0;
+    status.prec = 0;
+    status.stream = NULL;
+    va_start( status.arg, format );
+    memset( buffer, '\0', 100 );
+    if ( *(_PDCLIB_print( format, &status )) != '\0' )
+    {
+        printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
+        ++TEST_RESULTS;
+    }
+    va_end( status.arg );
+    return status.i;
+}
+#endif
+
+#define TEST_CONVERSION_ONLY
+
+int main( void )
+{
+#ifndef REGTEST
+    char target[100];
+#include "printf_testcases.h"
+#endif
+    return TEST_RESULTS;
+}
+
+#endif