Subversion Repositories Projects

Rev

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