X-Git-Url: https://pd.if.org/git/?p=pdclib;a=blobdiff_plain;f=functions%2F_PDCLIB%2Fprint.c;h=9a30ca2446b0d332784fdecebfa7d12b0b55b94f;hp=b0c3341d8dfc6dbbc923293054da65e39bbb9053;hb=15cd62244797b9aeb3279484f8e760778c52bc4e;hpb=9f91688d2e009a4c73162da5693bb8f717497586 diff --git a/functions/_PDCLIB/print.c b/functions/_PDCLIB/print.c index b0c3341..9a30ca2 100644 --- a/functions/_PDCLIB/print.c +++ b/functions/_PDCLIB/print.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /* _PDCLIB_print( const char *, struct _PDCLIB_status_t * ) This file is part of the Public Domain C Library (PDCLib). @@ -12,28 +10,34 @@ #include #include -extern char * _PDCLIB_Xdigits; +#ifndef REGTEST /* 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_intptr 1<<13 -#define E_double 1<<14 -#define E_lower 1<<15 -#define E_unsigned 1<<16 +/* 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_pointer (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). @@ -42,13 +46,106 @@ extern char * _PDCLIB_Xdigits; n - pointer to maximum number of characters to be delivered in this call s - the buffer into which the character shall be delivered */ -#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 ) +#define PUT( x ) \ +do { \ + int character = x; \ + if ( status->i < status->n ) { \ + if ( status->stream != NULL ) \ + putc( character, status->stream ); \ + else \ + status->s[status->i] = character; \ + } \ + ++(status->i); \ +} while ( 0 ) + + +static void intformat( intmax_t value, struct _PDCLIB_status_t * status ) +{ + /* 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 ) && ( value != 0 ) ) + { + /* Octal / hexadecimal prefix for "%#" conversions */ + preface[ preidx++ ] = '0'; + if ( status->base == 16 ) + { + preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X'; + } + } + 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++ ] = '+'; + } + else if ( status->flags & E_space ) + { + preface[ preidx++ ] = ' '; + } + } + { + size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 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->current > status->prec ) ? status->current : status->prec ); + if ( status->width > characters ) + { + for ( size_t i = 0; i < status->width - characters; ++i ) + { + PUT( ' ' ); + ++(status->current); + } + } + } + /* Now we did the padding, do the prefixes (if any). */ + preidx = 0; + while ( preface[ preidx ] != '\0' ) + { + PUT( preface[ preidx++ ] ); + ++(status->current); + } + if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) ) + { + /* If field is not left aligned, and zero padding is requested, do + so. + */ + while ( status->current < status->width ) + { + PUT( '0' ); + ++(status->current); + } + } + /* Do the precision padding if necessary. */ + for ( size_t i = 0; i < prec_pads; ++i ) + { + PUT( '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 _PDCLIB_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. +/* This function recursively converts a given integer value to a character + stream. The conversion is done under the control of a given status struct + and written either to a character string or a stream, depending on that + same status struct. The status struct also keeps the function from exceeding + snprintf() limits, and enables any necessary padding / prefixing of the + output once the number of characters to be printed is known, which happens + at the lowermost recursion level. */ static void int2base( intmax_t value, struct _PDCLIB_status_t * status ) { @@ -56,7 +153,7 @@ static void int2base( intmax_t value, struct _PDCLIB_status_t * status ) already so it will be taken into account when the deepestmost recursion does the prefix / padding stuff. */ - ++(status->this); + ++(status->current); if ( ( value / status->base ) != 0 ) { /* More digits to be done - recurse deeper */ @@ -69,81 +166,7 @@ static void int2base( intmax_t value, struct _PDCLIB_status_t * status ) 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 ) - { - preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X'; - } - } - 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++ ] = '+'; - } - else if ( status->flags & E_space ) - { - preface[ preidx++ ] = ' '; - } - } - { - 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 ) - { - 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' ) - { - DELIVER( preface[ preidx++ ] ); - ++(status->this); - } - 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' ); - } - } + intformat( value, status ); } /* Recursion tail - print the current digit. */ { @@ -155,31 +178,30 @@ static void int2base( intmax_t value, struct _PDCLIB_status_t * status ) if ( status->flags & E_lower ) { /* Lowercase letters. Same array used for strto...(). */ - DELIVER( _PDCLIB_digits[ digit ] ); + PUT( _PDCLIB_digits[ digit ] ); } else { /* Uppercase letters. Array only used here, only 0-F. */ - DELIVER( _PDCLIB_Xdigits[ digit ] ); + PUT( _PDCLIB_Xdigits[ digit ] ); } } } -/* This function is to be called with spec pointing to the leading '%' of a - printf() conversion specifier. -*/ + const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status ) { const char * orig_spec = spec; if ( *(++spec) == '%' ) { - DELIVER( *spec ); + /* %% -> print single '%' */ + PUT( *spec ); return ++spec; } /* Initializing status structure */ status->flags = 0; status->base = 0; - status->this = 0; + status->current = 0; status->width = 0; status->prec = 0; @@ -189,26 +211,32 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status 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; } @@ -218,11 +246,15 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status if ( *spec == '*' ) { /* Retrieve width value from argument stack */ - if ( ( status->width = va_arg( status->arg, int ) ) < 0 ) + int width = va_arg( status->arg, int ); + if ( width < 0 ) { - /* Negative value is '-' flag plus absolute value */ status->flags |= E_minus; - status->width *= -1; + status->width = abs( width ); + } + else + { + status->width = width; } ++spec; } @@ -259,7 +291,7 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status spec = endptr; } /* Having a precision cancels out any zero flag. */ - status->flags ^= E_zero; + status->flags &= ~E_zero; } /* Optional length modifier @@ -272,36 +304,44 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status 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': - status->flags |= E_double; + /* L -> long double */ + status->flags |= E_ldouble; break; default: --spec; @@ -344,7 +384,7 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status break; case 'c': /* TODO: Flags, wide chars. */ - DELIVER( va_arg( status->arg, int ) ); + PUT( va_arg( status->arg, int ) ); return ++spec; case 's': /* TODO: Flags, wide chars. */ @@ -352,14 +392,14 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status char * s = va_arg( status->arg, char * ); while ( *s != '\0' ) { - DELIVER( *(s++) ); + PUT( *(s++) ); } return ++spec; } case 'p': /* TODO: E_long -> E_intptr */ status->base = 16; - status->flags |= ( E_lower | E_unsigned | E_alt | E_long ); + status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer ); break; case 'n': { @@ -380,7 +420,7 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status if ( status->flags & E_unsigned ) { uintmax_t value; - switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) ) + switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) ) { case E_char: value = (uintmax_t)(unsigned char)va_arg( status->arg, int ); @@ -400,12 +440,24 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status case E_size: value = (uintmax_t)va_arg( status->arg, size_t ); break; + case E_pointer: + value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * ); + break; + default: + puts( "UNSUPPORTED PRINTF FLAG COMBINATION" ); + return NULL; } - ++(status->this); + ++(status->current); + /* FIXME: The if clause means one-digit values do not get formatted */ + /* Was introduced originally to get value to "safe" levels re. uintmax_t. */ if ( ( value / status->base ) != 0 ) { int2base( (intmax_t)(value / status->base), status ); } + else + { + intformat( (intmax_t)value, status ); + } int digit = value % status->base; if ( digit < 0 ) { @@ -413,11 +465,11 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status } if ( status->flags & E_lower ) { - DELIVER( _PDCLIB_digits[ digit ] ); + PUT( _PDCLIB_digits[ digit ] ); } else { - DELIVER( _PDCLIB_Xdigits[ digit ] ); + PUT( _PDCLIB_Xdigits[ digit ] ); } } else @@ -445,20 +497,72 @@ const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status case E_intmax: int2base( va_arg( status->arg, intmax_t ), status ); break; + default: + puts( "UNSUPPORTED PRINTF FLAG COMBINATION" ); + return NULL; } } if ( status->flags & E_minus ) { - while ( status->this < status->width ) + while ( status->current < status->width ) { - DELIVER( ' ' ); - ++(status->this); + PUT( ' ' ); + ++(status->current); } } - if ( status->i >= status->n ) + 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