+++ /dev/null
-/* $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