Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
212 dhylands 1
/****************************************************************************
2
*
3
*     Copyright (c) 2003 Dave Hylands
4
*           All Rights Reserved
5
*
6
*       Permission is granted to any individual or institution to use, copy, or
7
*       redistribute this software so long as it is not sold for profit, and that
8
*       this copyright notice is retained.
9
*
10
****************************************************************************/
11
/**
12
*
13
*  @file    StrPrintf.cpp
14
*
15
*  @brief   Implementation of a re-entrant printf function.
16
*
17
*  Implements a reentrant version of the printf function. Also allows a
18
*  function pointer to be provided to perform the actual output.
19
*
20
*  This version of printf was taken from
21
*
22
*     http://www.efgh.com/software/gprintf.htm
23
*
24
*  This software was posted by the author as being in the "public" domain.
25
*  I've taken the original gprintf.txt and made some minor revisions.
26
*
27
****************************************************************************/
28
 
29
/**
30
* @defgroup StrPrintf  String Formatting
31
* @ingroup  Str
32
*/
33
/**
34
*  @defgroup StrPrintfInternal   String Formatting Internals
35
*  @ingroup  StrPrintf
36
*/
37
 
38
/* ---- Include Files ---------------------------------------------------- */
39
 
40
#include "Str.h"
41
#include <limits.h>
42
#include <string.h>
43
 
44
/* ---- Public Variables ------------------------------------------------- */
45
/* ---- Private Constants and Types -------------------------------------- */
46
 
47
/**
48
 * @addtogroup StrPrintfInternal
49
 * @{
50
 */
51
 
52
/**
53
 * Controls a variety of output options.
54
 */
55
 
56
enum FmtOption
57
{
58
    NO_OPTION       = 0x00, /**< No options specified.                                          */
59
        MINUS_SIGN              = 0x01, /**< Should we print a minus sign?              */
60
        RIGHT_JUSTIFY   = 0x02, /**< Should field be right justified?           */
61
        ZERO_PAD        = 0x04, /**< Should field be zero padded?               */
62
        CAPITAL_HEX     = 0x08  /**< Did we encounter %X?                       */
63
 
64
};
65
 
66
/** @def IsOptionSet( p, x )   Determines if an option has been set.       */
67
/** @def IsOptionClear( p, x ) Determines if an option is not set.         */
68
/** @def SetOption( p, x )     Sets an option.                             */
69
/** @def ClearOption( p, x )   Unsets an option.                           */
70
 
71
#define  IsOptionSet( p, x )     (( (p)->options & (x)) != 0 )
72
#define  IsOptionClear( p, x )   (( (p)->options & (x)) == 0 )
73
#define  SetOption( p, x )       (p)->options = (FmtOption)((p)->options |  (x))
74
#define  ClearOption( p, x )     (p)->options = (FmtOption)((p)->options & ~(x))
75
 
76
/**
77
 * Internal structure which is used to allow vStrXPrintf() to be reentrant.
78
 */
79
 
80
typedef struct
81
{
82
   /** Number of characters output so far.                                 */
83
   int            numOutputChars;
84
 
85
   /** Options determined from parsing format specification.               */
86
   FmtOption      options;          
87
 
88
   /** Minimum number of characters to output.                             */
89
   short          minFieldWidth;    
90
 
91
   /** The exact number of characters to output.                           */
92
   short          editedStringLen;
93
 
94
   /** The number of leading zeros to output.                              */
95
   short          leadingZeros;
96
 
97
   /** The function to call to perform the actual output.                  */
98
   StrXPrintfFunc outFunc;
99
 
100
   /** Parameter to pass to the output function.                           */
101
   void          *outParm;
102
 
103
} Parameters;
104
 
105
/**
106
 * Internal structure used by vStrPrintf() .
107
 */
108
typedef struct
109
{
110
   char *str;     /**< Buffer to store results into.                       */
111
   int   maxLen;  /**< Maximum number of characters which can be stored.   */
112
 
113
} StrPrintfParms;
114
 
115
/* ---- Private Variables ------------------------------------------------ */
116
/* ---- Private Function Prototypes -------------------------------------- */
117
 
118
static void OutputChar( Parameters *p, int c );
119
static void OutputField( Parameters *p, char *s );
120
static int  StrPrintfFunc( void *outParm, int ch );
121
 
122
/** @} */
123
 
124
/* ---- Functions -------------------------------------------------------- */
125
 
126
/**
127
 * @addtogroup StrPrintf
128
 * @{
129
 */
130
 
131
/***************************************************************************/
132
/**
133
*  Writes formatted data into a user supplied buffer.
134
*
135
*  @param   outStr   (out) Place to store the formatted string.
136
*  @param   maxLen   (in)  Max number of characters to write into @a outStr.
137
*  @param   fmt      (in)  Format string (see vStrXPrintf() for sull details).
138
*/
139
 
140
int StrPrintf( char *outStr, int maxLen, const char *fmt, ... )
141
{
142
   int      rc;
143
   va_list  args;
144
 
145
   va_start( args, fmt );
146
   rc = vStrPrintf( outStr, maxLen, fmt, args );
147
   va_end( args );
148
 
149
   return rc;
150
 
151
} // StrPrintf
152
 
153
/***************************************************************************/
154
/**
155
*  Generic printf function which writes formatted data by calling a user
156
*  supplied function.
157
*
158
*  @a outFunc will be called to output each character. If @a outFunc returns
159
*  a number >= 0, then StrXPrintf will continue to call @a outFunc with
160
*  additional characters.
161
*
162
*  If @a outFunc returns a negative number, then StrXPrintf will stop
163
*  calling @a outFunc and will return the non-negative return value.
164
*
165
*  @param   outFunc  (in)  Pointer to function to call to do the actual output.
166
*  @param   outParm  (in)  Passed to @a outFunc.
167
*  @param   fmt      (in)  Format string (see vStrXPrintf() for sull details).
168
*
169
*/
170
 
171
int StrXPrintf( StrXPrintfFunc outFunc, void *outParm, const char *fmt, ... )
172
{
173
   int      rc;
174
   va_list  args;
175
 
176
   va_start( args, fmt );
177
   rc = vStrXPrintf( outFunc, outParm, fmt, args );
178
   va_end( args );
179
 
180
   return rc;
181
 
182
} // StrxPrintf
183
 
184
/***************************************************************************/
185
/**
186
*  Writes formatted data into a user supplied buffer.
187
*
188
*  @param   outStr   (out) Place to store the formatted string.
189
*  @param   maxLen   (in)  Max number of characters to write into @a outStr.
190
*  @param   fmt      (in)  Format string (see vStrXPrintf() for sull details).
191
*  @param   args     (in)  Arguments in a format compatible with va_arg().
192
*/
193
 
194
int vStrPrintf( char *outStr, int maxLen, const char *fmt, va_list args )
195
{
196
   StrPrintfParms    strParm;
197
 
198
   strParm.str    = outStr;
199
   strParm.maxLen = maxLen - 1; /* Leave space for temrinating null char   */
200
 
201
   return vStrXPrintf( StrPrintfFunc, &strParm, fmt, args );
202
 
203
} // vStrPrintf
204
 
205
/***************************************************************************/
206
/**
207
*  Generic, reentrant printf function. This is the workhorse of the StrPrintf
208
*  functions.
209
*
210
*  @a outFunc will be called to output each character. If @a outFunc returns
211
*  a number >= 0, then vStrXPrintf will continue to call @a outFunc with
212
*  additional characters.
213
*
214
*  If @a outFunc returns a negative number, then vStrXPrintf will stop calling
215
*  @a outFunc and will return the non-negative return value.
216
*
217
*  The format string @a fmt consists of ordinary characters, escape
218
*  sequences, and format specifications. The ordinary characters and escape
219
*  sequences are output in their order of appearance. Format specifications
220
*  start with a percent sign (%) and are read from left to right. When
221
*  the first format specification (if any) is encountered, it converts the
222
*  value of the first argument after @a fmt and outputs it accordingly.
223
*  The second format specification causes the second argument to be
224
*  converted and output, and so on. If there are more arguments than there
225
*  are format specifications, the extra arguments are ignored. The
226
*  results are undefined if there are not enough arguments for all the
227
*  format specifications.
228
*
229
*  A format specification has optional, and required fields, in the following
230
*  form:
231
*
232
*     %[flags][width][.precision][l]type
233
*
234
*  Each field of the format specification is a single character or a number
235
*  specifying a particular format option. The simplest format specification
236
*  contains only the percent sign and a @b type character (for example %s).
237
*  If a percent sign is followed by a character that has no meaning as a
238
*  format field, the character is sent to the output function. For example,
239
*  to print a percent-sign character, use %%.
240
*
241
*  The optional fields, which appear before the type character, control
242
*  other aspects of the formatting, as follows:
243
*
244
*  @b flags may be one of the following:
245
*
246
*  - - (minus sign) left align the result within the given field width.
247
*  - 0 (zero) Zeros are added until the minimum width is reached.
248
*
249
*  @b width may be one of the following:
250
*  - a number specifying the minimum width of the field
251
*  - * (asterick) means that an integer taken from the argument list will
252
*    be used to provide the width. The @a width argument must precede the
253
*    value being formatted in the argument list.
254
*
255
*  @b precision may be one of the following:
256
*  - a number
257
*  - * (asterick) means that an integer taken from the argument list will
258
*    be used to provide the precision. The @a precision argument must
259
*    precede the value being formatted in the argument list.
260
*
261
*  The interpretation of @a precision depends on the type of field being
262
*  formatted:
263
*  - For b, d, o, u, x, X, the precision specifies the minimum number of
264
*    digits that will be printed. If the number of digits in the argument
265
*    is less than @a precision, the output value is padded on the left with
266
*    zeros. The value is not truncated when the number of digits exceeds
267
*    @a prcision.
268
*  - For s, the precision specifies the maximum number of characters to be
269
*    printed.
270
*
271
*  The optional type modifier l (lowercase ell), may be used to specify
272
*  that the argument is a long argument. This makes a difference on
273
*  architectures where the sizeof an int is different from the sizeof a long.
274
*
275
*  @b type causes the output to be formatted as follows:
276
*  - b Unsigned binary integer.
277
*  - c Character.
278
*  - d Signed decimal integer.
279
*  - o Unsigned octal integer.
280
*  - s Null terminated character string.
281
*  - u Unsigned Decimal integer.
282
*  - x Unsigned hexadecimal integer, using "abcdef".
283
*  - X Unsigned hexadecimal integer, using "ABCDEF".
284
*
285
*  @param   outFunc     (in) Pointer to function to call to output a character.
286
*  @param   outParm     (in) Passed to @a outFunc.
287
*  @param   fmt         (in) Format string (ala printf, descrtibed above).
288
*  @param   args        (in) Variable length list of arguments.
289
*
290
*  @return  The number of characters successfully output, or a negative number
291
*           if an error occurred.
292
*/
293
 
294
int vStrXPrintf
295
(
296
   StrXPrintfFunc outFunc,
297
   void          *outParm,
298
   const char    *fmt,
299
   va_list        args
300
)
301
{
302
   Parameters  p;
303
   char        controlChar;
304
 
305
   p.numOutputChars  = 0;
306
   p.outFunc         = outFunc;
307
   p.outParm         = outParm;
308
 
309
   controlChar = *fmt++;
310
 
311
   while ( controlChar != '\0' )
312
   {
313
      if ( controlChar == '%' )
314
      {
315
         short precision = -1;
316
         short longArg   = 0;
317
         short base      = 0;
318
 
319
         controlChar = *fmt++;
320
         p.minFieldWidth = 0;
321
         p.leadingZeros  = 0;
322
         p.options       = NO_OPTION;
323
 
324
         SetOption( &p, RIGHT_JUSTIFY );
325
 
326
         /*
327
          * Process [flags]
328
          */
329
 
330
         if ( controlChar == '-' )
331
         {
332
            ClearOption( &p, RIGHT_JUSTIFY );
333
            controlChar = *fmt++;
334
         }
335
 
336
         if ( controlChar == '0' )
337
         {
338
            SetOption( &p, ZERO_PAD );
339
            controlChar = *fmt++;
340
         }
341
 
342
         /*
343
          * Process [width]
344
          */
345
 
346
         if ( controlChar == '*' )
347
         {
348
            p.minFieldWidth = (short)va_arg( args, int );
349
            controlChar = *fmt++;
350
         }
351
         else
352
         {
353
            while (( '0' <= controlChar ) && ( controlChar <= '9' ))
354
            {
355
               p.minFieldWidth =
356
                  p.minFieldWidth * 10 + controlChar - '0';
357
               controlChar = *fmt++;
358
            }
359
         }
360
 
361
         /*
362
          * Process [.precision]
363
          */
364
 
365
         if ( controlChar == '.' )
366
         {
367
            controlChar = *fmt++;
368
            if ( controlChar == '*' )
369
            {
370
               precision = (short)va_arg( args, int );
371
               controlChar = *fmt++;
372
            }
373
            else
374
            {
375
               precision = 0;
376
               while (( '0' <= controlChar ) && ( controlChar <= '9' ))
377
               {
378
                  precision = precision * 10 + controlChar - '0';
379
                  controlChar = *fmt++;
380
               }
381
            }
382
         }
383
 
384
         /*
385
          * Process [l]
386
          */
387
 
388
         if ( controlChar == 'l' )
389
         {
390
            longArg = 1;
391
            controlChar = *fmt++;
392
         }
393
 
394
         /*
395
          * Process type.
396
          */
397
 
398
         if ( controlChar == 'd' )
399
         {
400
            base = 10;
401
         }
402
         else
403
         if ( controlChar == 'x' )
404
         {
405
            base = 16;
406
         }
407
         else
408
         if ( controlChar == 'X' )
409
         {
410
            base = 16;
411
            SetOption( &p, CAPITAL_HEX );
412
         }
413
         else
414
         if ( controlChar == 'u' )
415
         {
416
            base = 10;
417
         }
418
         else
419
         if ( controlChar == 'o' )
420
         {
421
            base = 8;
422
         }
423
         else
424
         if ( controlChar == 'b' )
425
         {
426
            base = 2;
427
         }
428
         else
429
         if ( controlChar == 'c' )
430
         {
431
            base = -1;
432
            ClearOption( &p, ZERO_PAD );
433
         }
434
         else
435
         if ( controlChar == 's' )
436
         {
437
            base = -2;
438
            ClearOption( &p, ZERO_PAD );
439
         }
440
 
441
         if ( base == 0 )  /* invalid conversion type */
442
         {
443
            if ( controlChar != '\0' )
444
            {
445
               OutputChar( &p, controlChar );
446
               controlChar = *fmt++;
447
            }
448
         }
449
         else
450
         {
451
            if ( base == -1 )  /* conversion type c */
452
            {
453
               char c = (char)va_arg( args, int );
454
               p.editedStringLen = 1;
455
               OutputField( &p, &c );
456
            }
457
            else if ( base == -2 )  /* conversion type s */
458
            {
459
               char *string = va_arg( args, char * );
460
 
461
               p.editedStringLen = 0;
462
               while ( string[ p.editedStringLen ] != '\0' )
463
               {
464
                  if (( precision >= 0 ) && ( p.editedStringLen >= precision ))
465
                  {
466
                     /*
467
                      * We don't require the string to be null terminated
468
                      * if a precision is specified.
469
                      */
470
 
471
                     break;
472
                  }
473
                  p.editedStringLen++;
474
               }
475
               OutputField( &p, string );
476
            }
477
            else  /* conversion type d, b, o or x */
478
            {
479
               unsigned long x;
480
 
481
               /*
482
                * Worst case buffer allocation is required for binary output,
483
                * which requires one character per bit of a long.
484
                */
485
 
486
               char buffer[ CHAR_BIT * sizeof( unsigned long ) + 1 ];
487
 
488
               p.editedStringLen = 0;
489
               if ( longArg )
490
               {
491
                  x = va_arg( args, unsigned long );
492
               }
493
               else
494
               if ( controlChar == 'd' )
495
               {
496
                  x = va_arg( args, int );
497
               }
498
               else
499
               {
500
                  x = va_arg( args, unsigned );
501
               }
502
 
503
               if (( controlChar == 'd' ) && ((long) x < 0 ))
504
               {
505
                  SetOption( &p, MINUS_SIGN );
506
                  x = - (long) x;
507
               }
508
 
509
               do
510
               {
511
                  int c;
512
                  c = x % base + '0';
513
                  if ( c > '9' )
514
                  {
515
                     if ( IsOptionSet( &p, CAPITAL_HEX ))
516
                     {
517
                        c += 'A'-'9'-1;
518
                     }
519
                     else
520
                     {
521
                        c += 'a'-'9'-1;
522
                     }
523
                  }
524
                  buffer[ sizeof( buffer ) - 1 - p.editedStringLen++ ] = (char)c;
525
               }
526
               while (( x /= base ) != 0 );
527
 
528
               if (( precision >= 0 ) && ( precision > p.editedStringLen ))
529
               {
530
                  p.leadingZeros = precision - p.editedStringLen;
531
               }
532
               OutputField( &p, buffer + sizeof(buffer) - p.editedStringLen );
533
            }
534
            controlChar = *fmt++;
535
         }
536
      }
537
      else
538
      {
539
         /*
540
          * We're not processing a % output. Just output the character that
541
          * was encountered.
542
          */
543
 
544
         OutputChar( &p, controlChar );
545
         controlChar = *fmt++;
546
      }
547
   }
548
   return p.numOutputChars;
549
 
550
} // vStrXPrintf
551
 
552
/** @} */
553
 
554
/**
555
 * @addtogroup StrPrintfInternal
556
 * @{
557
 */
558
 
559
/***************************************************************************/
560
/**
561
*  Outputs a single character, keeping track of how many characters have
562
* been output.
563
*
564
*  @param   p     (mod) State information.
565
*  @param   c     (in)  Character to output.
566
*/
567
 
568
static void OutputChar( Parameters *p, int c )
569
{
570
   if ( p->numOutputChars >= 0 )
571
   {
572
      int n = (*p->outFunc)(p->outParm, c);
573
 
574
      if ( n >= 0 )
575
      {
576
         p->numOutputChars++;
577
      }
578
      else
579
      {
580
         p->numOutputChars = n;
581
      }
582
   }
583
 
584
} // OutputChar
585
 
586
/***************************************************************************/
587
/**
588
*  Outputs a formatted field. This routine assumes that the field has been
589
*  converted to a string, and this routine takes care of the width
590
*  options, leading zeros, and any leading minus sign.
591
*
592
*  @param   p     (mod) State information.
593
*  @param   s     (in)  String to output.
594
*/
595
 
596
static void OutputField( Parameters *p, char *s )
597
{
598
   short padLen = p->minFieldWidth - p->leadingZeros - p->editedStringLen;
599
 
600
   if ( IsOptionSet( p, MINUS_SIGN ))
601
   {
602
      if ( IsOptionSet( p, ZERO_PAD ))
603
      {
604
         /*
605
          * Since we're zero padding, output the minus sign now. If we're space
606
          * padding, we wait until we've output the spaces.
607
          */
608
 
609
         OutputChar( p, '-' );
610
      }
611
 
612
      /*
613
       * Account for the minus sign now, even if we are going to output it
614
       * later. Otherwise we'll output too much space padding.
615
       */
616
 
617
      padLen--;
618
   }
619
 
620
   if ( IsOptionSet( p, RIGHT_JUSTIFY ))
621
   {
622
      /*
623
       * Right justified: Output the spaces then the field.
624
       */
625
 
626
      while ( --padLen >= 0 )
627
      {
628
         OutputChar( p, p->options & ZERO_PAD ? '0' : ' ' );
629
      }
630
   }
631
   if ( IsOptionSet( p, MINUS_SIGN ) && IsOptionClear( p, ZERO_PAD ))
632
   {
633
      /*
634
       * We're not zero padding, which means we haven't output the minus
635
       * sign yet. Do it now.
636
       */
637
 
638
      OutputChar( p, '-' );
639
   }
640
 
641
   /*
642
    * Output any leading zeros.
643
    */
644
 
645
   while ( --p->leadingZeros >= 0 )
646
   {
647
      OutputChar( p, '0' );
648
   }
649
 
650
   /*
651
    * Output the field itself.
652
    */
653
 
654
   while ( --p->editedStringLen >= 0 )
655
   {
656
      OutputChar( p, *s++ );
657
   }
658
 
659
   /*
660
    * Output any trailing space padding. Note that if we output leading
661
    * padding, then padLen will already have been decremented to zero.
662
    */
663
 
664
   while ( --padLen >= 0 )
665
   {
666
      OutputChar( p, ' ' );
667
   }
668
 
669
} // OutputField
670
 
671
/***************************************************************************/
672
/**
673
*  Helper function, used by vStrPrintf() (and indirectly by StrPrintf())
674
*  for outputting characters into a user supplied buffer.
675
*
676
*  @param   outParm  (mod) Pointer to StrPrintfParms structure.
677
*  @param   ch       (in)  Character to output.
678
*
679
*  @return  1 if the character was stored successfully, -1 if the buffer
680
*           was overflowed.
681
*/
682
 
683
static int StrPrintfFunc( void *outParm, int ch )
684
{
685
   StrPrintfParms *strParm = static_cast< StrPrintfParms * >( outParm );
686
 
687
   if ( strParm->maxLen > 0 )
688
   {
689
      *strParm->str++ = (char)ch;
690
      *strParm->str = '\0';
691
      strParm->maxLen--;
692
 
693
      return 1;
694
   }
695
 
696
   /*
697
    * Whoops. We ran out of space.
698
    */
699
 
700
   return -1;
701
 
702
} // StrPrintfFunc
703