Blame |
Last modification |
View Log
| RSS feed
/****************************************************************************
*
* Copyright (c) 2003 Dave Hylands
* All Rights Reserved
*
* Permission is granted to any individual or institution to use, copy, or
* redistribute this software so long as it is not sold for profit, and that
* this copyright notice is retained.
*
****************************************************************************/
/**
*
* @file StrPrintf.cpp
*
* @brief Implementation of a re-entrant printf function.
*
* Implements a reentrant version of the printf function. Also allows a
* function pointer to be provided to perform the actual output.
*
* This version of printf was taken from
*
* http://www.efgh.com/software/gprintf.htm
*
* This software was posted by the author as being in the "public" domain.
* I've taken the original gprintf.txt and made some minor revisions.
*
****************************************************************************/
/**
* @defgroup StrPrintf String Formatting
* @ingroup Str
*/
/**
* @defgroup StrPrintfInternal String Formatting Internals
* @ingroup StrPrintf
*/
/* ---- Include Files ---------------------------------------------------- */
#include "Str.h"
#include <limits.h>
#include <string.h>
/* ---- Public Variables ------------------------------------------------- */
/* ---- Private Constants and Types -------------------------------------- */
/**
* @addtogroup StrPrintfInternal
* @{
*/
/**
* Controls a variety of output options.
*/
enum FmtOption
{
NO_OPTION = 0x00, /**< No options specified. */
MINUS_SIGN = 0x01, /**< Should we print a minus sign? */
RIGHT_JUSTIFY = 0x02, /**< Should field be right justified? */
ZERO_PAD = 0x04, /**< Should field be zero padded? */
CAPITAL_HEX = 0x08 /**< Did we encounter %X? */
};
/** @def IsOptionSet( p, x ) Determines if an option has been set. */
/** @def IsOptionClear( p, x ) Determines if an option is not set. */
/** @def SetOption( p, x ) Sets an option. */
/** @def ClearOption( p, x ) Unsets an option. */
#define IsOptionSet( p, x ) (( (p)->options & (x)) != 0 )
#define IsOptionClear( p, x ) (( (p)->options & (x)) == 0 )
#define SetOption( p, x ) (p)->options = (FmtOption)((p)->options | (x))
#define ClearOption( p, x ) (p)->options = (FmtOption)((p)->options & ~(x))
/**
* Internal structure which is used to allow vStrXPrintf() to be reentrant.
*/
typedef struct
{
/** Number of characters output so far. */
int numOutputChars;
/** Options determined from parsing format specification. */
FmtOption options;
/** Minimum number of characters to output. */
short minFieldWidth;
/** The exact number of characters to output. */
short editedStringLen;
/** The number of leading zeros to output. */
short leadingZeros;
/** The function to call to perform the actual output. */
StrXPrintfFunc outFunc;
/** Parameter to pass to the output function. */
void *outParm;
} Parameters;
/**
* Internal structure used by vStrPrintf() .
*/
typedef struct
{
char *str; /**< Buffer to store results into. */
int maxLen; /**< Maximum number of characters which can be stored. */
} StrPrintfParms;
/* ---- Private Variables ------------------------------------------------ */
/* ---- Private Function Prototypes -------------------------------------- */
static void OutputChar( Parameters *p, int c );
static void OutputField( Parameters *p, char *s );
static int StrPrintfFunc( void *outParm, int ch );
/** @} */
/* ---- Functions -------------------------------------------------------- */
/**
* @addtogroup StrPrintf
* @{
*/
/***************************************************************************/
/**
* Writes formatted data into a user supplied buffer.
*
* @param outStr (out) Place to store the formatted string.
* @param maxLen (in) Max number of characters to write into @a outStr.
* @param fmt (in) Format string (see vStrXPrintf() for sull details).
*/
int StrPrintf( char *outStr, int maxLen, const char *fmt, ... )
{
int rc;
va_list args;
va_start( args, fmt );
rc = vStrPrintf( outStr, maxLen, fmt, args );
va_end( args );
return rc;
} // StrPrintf
/***************************************************************************/
/**
* Generic printf function which writes formatted data by calling a user
* supplied function.
*
* @a outFunc will be called to output each character. If @a outFunc returns
* a number >= 0, then StrXPrintf will continue to call @a outFunc with
* additional characters.
*
* If @a outFunc returns a negative number, then StrXPrintf will stop
* calling @a outFunc and will return the non-negative return value.
*
* @param outFunc (in) Pointer to function to call to do the actual output.
* @param outParm (in) Passed to @a outFunc.
* @param fmt (in) Format string (see vStrXPrintf() for sull details).
*
*/
int StrXPrintf( StrXPrintfFunc outFunc, void *outParm, const char *fmt, ... )
{
int rc;
va_list args;
va_start( args, fmt );
rc = vStrXPrintf( outFunc, outParm, fmt, args );
va_end( args );
return rc;
} // StrxPrintf
/***************************************************************************/
/**
* Writes formatted data into a user supplied buffer.
*
* @param outStr (out) Place to store the formatted string.
* @param maxLen (in) Max number of characters to write into @a outStr.
* @param fmt (in) Format string (see vStrXPrintf() for sull details).
* @param args (in) Arguments in a format compatible with va_arg().
*/
int vStrPrintf( char *outStr, int maxLen, const char *fmt, va_list args )
{
StrPrintfParms strParm;
strParm.str = outStr;
strParm.maxLen = maxLen - 1; /* Leave space for temrinating null char */
return vStrXPrintf( StrPrintfFunc, &strParm, fmt, args );
} // vStrPrintf
/***************************************************************************/
/**
* Generic, reentrant printf function. This is the workhorse of the StrPrintf
* functions.
*
* @a outFunc will be called to output each character. If @a outFunc returns
* a number >= 0, then vStrXPrintf will continue to call @a outFunc with
* additional characters.
*
* If @a outFunc returns a negative number, then vStrXPrintf will stop calling
* @a outFunc and will return the non-negative return value.
*
* The format string @a fmt consists of ordinary characters, escape
* sequences, and format specifications. The ordinary characters and escape
* sequences are output in their order of appearance. Format specifications
* start with a percent sign (%) and are read from left to right. When
* the first format specification (if any) is encountered, it converts the
* value of the first argument after @a fmt and outputs it accordingly.
* The second format specification causes the second argument to be
* converted and output, and so on. If there are more arguments than there
* are format specifications, the extra arguments are ignored. The
* results are undefined if there are not enough arguments for all the
* format specifications.
*
* A format specification has optional, and required fields, in the following
* form:
*
* %[flags][width][.precision][l]type
*
* Each field of the format specification is a single character or a number
* specifying a particular format option. The simplest format specification
* contains only the percent sign and a @b type character (for example %s).
* If a percent sign is followed by a character that has no meaning as a
* format field, the character is sent to the output function. For example,
* to print a percent-sign character, use %%.
*
* The optional fields, which appear before the type character, control
* other aspects of the formatting, as follows:
*
* @b flags may be one of the following:
*
* - - (minus sign) left align the result within the given field width.
* - 0 (zero) Zeros are added until the minimum width is reached.
*
* @b width may be one of the following:
* - a number specifying the minimum width of the field
* - * (asterick) means that an integer taken from the argument list will
* be used to provide the width. The @a width argument must precede the
* value being formatted in the argument list.
*
* @b precision may be one of the following:
* - a number
* - * (asterick) means that an integer taken from the argument list will
* be used to provide the precision. The @a precision argument must
* precede the value being formatted in the argument list.
*
* The interpretation of @a precision depends on the type of field being
* formatted:
* - For b, d, o, u, x, X, the precision specifies the minimum number of
* digits that will be printed. If the number of digits in the argument
* is less than @a precision, the output value is padded on the left with
* zeros. The value is not truncated when the number of digits exceeds
* @a prcision.
* - For s, the precision specifies the maximum number of characters to be
* printed.
*
* The optional type modifier l (lowercase ell), may be used to specify
* that the argument is a long argument. This makes a difference on
* architectures where the sizeof an int is different from the sizeof a long.
*
* @b type causes the output to be formatted as follows:
* - b Unsigned binary integer.
* - c Character.
* - d Signed decimal integer.
* - o Unsigned octal integer.
* - s Null terminated character string.
* - u Unsigned Decimal integer.
* - x Unsigned hexadecimal integer, using "abcdef".
* - X Unsigned hexadecimal integer, using "ABCDEF".
*
* @param outFunc (in) Pointer to function to call to output a character.
* @param outParm (in) Passed to @a outFunc.
* @param fmt (in) Format string (ala printf, descrtibed above).
* @param args (in) Variable length list of arguments.
*
* @return The number of characters successfully output, or a negative number
* if an error occurred.
*/
int vStrXPrintf
(
StrXPrintfFunc outFunc,
void *outParm,
const char *fmt,
va_list args
)
{
Parameters p;
char controlChar;
p.numOutputChars = 0;
p.outFunc = outFunc;
p.outParm = outParm;
controlChar = *fmt++;
while ( controlChar != '\0' )
{
if ( controlChar == '%' )
{
short precision = -1;
short longArg = 0;
short base = 0;
controlChar = *fmt++;
p.minFieldWidth = 0;
p.leadingZeros = 0;
p.options = NO_OPTION;
SetOption( &p, RIGHT_JUSTIFY );
/*
* Process [flags]
*/
if ( controlChar == '-' )
{
ClearOption( &p, RIGHT_JUSTIFY );
controlChar = *fmt++;
}
if ( controlChar == '0' )
{
SetOption( &p, ZERO_PAD );
controlChar = *fmt++;
}
/*
* Process [width]
*/
if ( controlChar == '*' )
{
p.minFieldWidth = (short)va_arg( args, int );
controlChar = *fmt++;
}
else
{
while (( '0' <= controlChar ) && ( controlChar <= '9' ))
{
p.minFieldWidth =
p.minFieldWidth * 10 + controlChar - '0';
controlChar = *fmt++;
}
}
/*
* Process [.precision]
*/
if ( controlChar == '.' )
{
controlChar = *fmt++;
if ( controlChar == '*' )
{
precision = (short)va_arg( args, int );
controlChar = *fmt++;
}
else
{
precision = 0;
while (( '0' <= controlChar ) && ( controlChar <= '9' ))
{
precision = precision * 10 + controlChar - '0';
controlChar = *fmt++;
}
}
}
/*
* Process [l]
*/
if ( controlChar == 'l' )
{
longArg = 1;
controlChar = *fmt++;
}
/*
* Process type.
*/
if ( controlChar == 'd' )
{
base = 10;
}
else
if ( controlChar == 'x' )
{
base = 16;
}
else
if ( controlChar == 'X' )
{
base = 16;
SetOption( &p, CAPITAL_HEX );
}
else
if ( controlChar == 'u' )
{
base = 10;
}
else
if ( controlChar == 'o' )
{
base = 8;
}
else
if ( controlChar == 'b' )
{
base = 2;
}
else
if ( controlChar == 'c' )
{
base = -1;
ClearOption( &p, ZERO_PAD );
}
else
if ( controlChar == 's' )
{
base = -2;
ClearOption( &p, ZERO_PAD );
}
if ( base == 0 ) /* invalid conversion type */
{
if ( controlChar != '\0' )
{
OutputChar( &p, controlChar );
controlChar = *fmt++;
}
}
else
{
if ( base == -1 ) /* conversion type c */
{
char c = (char)va_arg( args, int );
p.editedStringLen = 1;
OutputField( &p, &c );
}
else if ( base == -2 ) /* conversion type s */
{
char *string = va_arg( args, char * );
p.editedStringLen = 0;
while ( string[ p.editedStringLen ] != '\0' )
{
if (( precision >= 0 ) && ( p.editedStringLen >= precision ))
{
/*
* We don't require the string to be null terminated
* if a precision is specified.
*/
break;
}
p.editedStringLen++;
}
OutputField( &p, string );
}
else /* conversion type d, b, o or x */
{
unsigned long x;
/*
* Worst case buffer allocation is required for binary output,
* which requires one character per bit of a long.
*/
char buffer[ CHAR_BIT * sizeof( unsigned long ) + 1 ];
p.editedStringLen = 0;
if ( longArg )
{
x = va_arg( args, unsigned long );
}
else
if ( controlChar == 'd' )
{
x = va_arg( args, int );
}
else
{
x = va_arg( args, unsigned );
}
if (( controlChar == 'd' ) && ((long) x < 0 ))
{
SetOption( &p, MINUS_SIGN );
x = - (long) x;
}
do
{
int c;
c = x % base + '0';
if ( c > '9' )
{
if ( IsOptionSet( &p, CAPITAL_HEX ))
{
c += 'A'-'9'-1;
}
else
{
c += 'a'-'9'-1;
}
}
buffer[ sizeof( buffer ) - 1 - p.editedStringLen++ ] = (char)c;
}
while (( x /= base ) != 0 );
if (( precision >= 0 ) && ( precision > p.editedStringLen ))
{
p.leadingZeros = precision - p.editedStringLen;
}
OutputField( &p, buffer + sizeof(buffer) - p.editedStringLen );
}
controlChar = *fmt++;
}
}
else
{
/*
* We're not processing a % output. Just output the character that
* was encountered.
*/
OutputChar( &p, controlChar );
controlChar = *fmt++;
}
}
return p.numOutputChars;
} // vStrXPrintf
/** @} */
/**
* @addtogroup StrPrintfInternal
* @{
*/
/***************************************************************************/
/**
* Outputs a single character, keeping track of how many characters have
* been output.
*
* @param p (mod) State information.
* @param c (in) Character to output.
*/
static void OutputChar( Parameters *p, int c )
{
if ( p->numOutputChars >= 0 )
{
int n = (*p->outFunc)(p->outParm, c);
if ( n >= 0 )
{
p->numOutputChars++;
}
else
{
p->numOutputChars = n;
}
}
} // OutputChar
/***************************************************************************/
/**
* Outputs a formatted field. This routine assumes that the field has been
* converted to a string, and this routine takes care of the width
* options, leading zeros, and any leading minus sign.
*
* @param p (mod) State information.
* @param s (in) String to output.
*/
static void OutputField( Parameters *p, char *s )
{
short padLen = p->minFieldWidth - p->leadingZeros - p->editedStringLen;
if ( IsOptionSet( p, MINUS_SIGN ))
{
if ( IsOptionSet( p, ZERO_PAD ))
{
/*
* Since we're zero padding, output the minus sign now. If we're space
* padding, we wait until we've output the spaces.
*/
OutputChar( p, '-' );
}
/*
* Account for the minus sign now, even if we are going to output it
* later. Otherwise we'll output too much space padding.
*/
padLen--;
}
if ( IsOptionSet( p, RIGHT_JUSTIFY ))
{
/*
* Right justified: Output the spaces then the field.
*/
while ( --padLen >= 0 )
{
OutputChar( p, p->options & ZERO_PAD ? '0' : ' ' );
}
}
if ( IsOptionSet( p, MINUS_SIGN ) && IsOptionClear( p, ZERO_PAD ))
{
/*
* We're not zero padding, which means we haven't output the minus
* sign yet. Do it now.
*/
OutputChar( p, '-' );
}
/*
* Output any leading zeros.
*/
while ( --p->leadingZeros >= 0 )
{
OutputChar( p, '0' );
}
/*
* Output the field itself.
*/
while ( --p->editedStringLen >= 0 )
{
OutputChar( p, *s++ );
}
/*
* Output any trailing space padding. Note that if we output leading
* padding, then padLen will already have been decremented to zero.
*/
while ( --padLen >= 0 )
{
OutputChar( p, ' ' );
}
} // OutputField
/***************************************************************************/
/**
* Helper function, used by vStrPrintf() (and indirectly by StrPrintf())
* for outputting characters into a user supplied buffer.
*
* @param outParm (mod) Pointer to StrPrintfParms structure.
* @param ch (in) Character to output.
*
* @return 1 if the character was stored successfully, -1 if the buffer
* was overflowed.
*/
static int StrPrintfFunc( void *outParm, int ch )
{
StrPrintfParms *strParm = static_cast< StrPrintfParms * >( outParm );
if ( strParm->maxLen > 0 )
{
*strParm->str++ = (char)ch;
*strParm->str = '\0';
strParm->maxLen--;
return 1;
}
/*
* Whoops. We ran out of space.
*/
return -1;
} // StrPrintfFunc