Rewrite int printing to be non-recursive. Limit ints to 32 bits
[fw/pdclib] / functions / _PDCLIB / print.c
1 /* $Id$ */
2
3 /* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )
4
5    This file is part of the Public Domain C Library (PDCLib).
6    Permission is granted to use, modify, and / or redistribute at will.
7 */
8
9 #include <stdio.h>
10 #include <stdint.h>
11 #include <stdarg.h>
12 #include <stdlib.h>
13 #include <stddef.h>
14
15 /* Using an integer's bits as flags for both the conversion flags and length
16    modifiers.
17 */
18 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
19           width flags) into a combined field.
20 */
21 #define E_minus    1<<0
22 #define E_plus     1<<1
23 #define E_alt      1<<2
24 #define E_space    1<<3
25 #define E_zero     1<<4
26 #define E_done     1<<5
27 #define E_char     1<<6
28 #define E_short    1<<7
29 #define E_long     1<<8
30 #define E_llong    1<<9
31 #define E_intmax   1<<10
32 #define E_size     1<<11
33 #define E_ptrdiff  1<<12
34 #define E_intptr   1<<13
35 #define E_ldouble  1<<14
36 #define E_lower    1<<15
37 #define E_unsigned 1<<16
38
39 /* This macro delivers a given character to either a memory buffer or a stream,
40    depending on the contents of 'status' (struct _PDCLIB_status_t).
41    x - the character to be delivered
42    i - pointer to number of characters already delivered in this call
43    n - pointer to maximum number of characters to be delivered in this call
44    s - the buffer into which the character shall be delivered
45 */
46 #define PUT( x ) \
47 do { \
48     int character = x; \
49     if ( status->i < status->n ) { \
50         if ( status->stream != NULL ) \
51             putc( character, status->stream ); \
52         else \
53             status->s[status->i] = character; \
54     } \
55     ++(status->i); \
56 } while ( 0 )
57
58
59 typedef int32_t intfmt_t;
60 typedef uint32_t uintfmt_t;
61
62 static void intformat( intfmt_t value, struct _PDCLIB_status_t * status )
63 {
64     /* At worst, we need two prefix characters (hex prefix). */
65     char preface[3] = "\0";
66     size_t preidx = 0;
67     if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
68     {
69         /* Octal / hexadecimal prefix for "%#" conversions */
70         preface[ preidx++ ] = '0';
71         if ( status->base == 16 )
72         {
73             preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
74         }
75     }
76     if ( value < 0 )
77     {
78         /* Negative sign for negative values - at all times. */
79         preface[ preidx++ ] = '-';
80     }
81     else if ( ! ( status->flags & E_unsigned ) )
82     {
83         /* plus sign / extra space are only for unsigned conversions */
84         if ( status->flags & E_plus )
85         {
86             preface[ preidx++ ] = '+';
87         }
88         else if ( status->flags & E_space )
89         {
90             preface[ preidx++ ] = ' ';
91         }
92     }
93     {
94     size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
95     if ( ! ( status->flags & ( E_minus | E_zero ) ) )
96     {
97         /* Space padding is only done if no zero padding or left alignment
98            is requested. Leave space for any prefixes determined above.
99         */
100         /* The number of characters to be printed, plus prefixes if any. */
101         /* This line contained probably the most stupid, time-wasting bug
102            I've ever perpetrated. Greetings to Samface, DevL, and all
103            sceners at Breakpoint 2006.
104         */
105         size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
106         if ( status->width > characters )
107         {
108             for ( size_t i = 0; i < status->width - characters; ++i )
109             {
110                 PUT( ' ' );
111                 ++(status->current);
112             }
113         }
114     }
115     /* Now we did the padding, do the prefixes (if any). */
116     preidx = 0;
117     while ( preface[ preidx ] != '\0' )
118     {
119         PUT( preface[ preidx++ ] );
120         ++(status->current);
121     }
122     if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
123     {
124         /* If field is not left aligned, and zero padding is requested, do
125            so.
126         */
127         while ( status->current < status->width )
128         {
129             PUT( '0' );
130             ++(status->current);
131         }
132     }
133     /* Do the precision padding if necessary. */
134     for ( size_t i = 0; i < prec_pads; ++i )
135     {
136         PUT( '0' );
137     }
138     }
139 }
140
141
142 /* This function recursively converts a given integer value to a character
143    stream. The conversion is done under the control of a given status struct
144    and written either to a character string or a stream, depending on that
145    same status struct. The status struct also keeps the function from exceeding
146    snprintf() limits, and enables any necessary padding / prefixing of the
147    output once the number of characters to be printed is known, which happens
148    at the lowermost recursion level.
149 */
150
151 #define INT_MAX_WIDTH   (sizeof(intfmt_t) * 8 + 2) / 3
152
153 static void int2base( intfmt_t value, struct _PDCLIB_status_t * status )
154 {
155     char digits[INT_MAX_WIDTH + 1];
156     char c, *d = &digits[sizeof(digits)];
157     intfmt_t last_value = value;
158
159     *--d = '\0';
160
161     do {
162         int digit = value % status->base;
163
164         last_value = value;
165         value /= status->base;
166
167         if (digit < 0)
168             digit = -digit; 
169
170         /*
171           Account for character in the output
172          */
173
174         ++(status->current);
175         if ( (status->flags & E_lower) )
176             *--d = _PDCLIB_digits[digit];
177         else
178             *--d = _PDCLIB_Xdigits[digit];
179     } while (value != 0);
180
181     /* We reached the last digit, and only now know how long the
182        number to be printed actually is. Now we have to do the sign,
183        prefix, width, and precision padding stuff before printing the
184        digits
185     */
186
187     intformat( last_value, status );
188
189     while ( (c = *d++) )
190         PUT(c);
191 }
192
193
194 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
195 {
196     const char * orig_spec = spec;
197     if ( *(++spec) == '%' )
198     {
199         /* %% -> print single '%' */
200         PUT( *spec );
201         return ++spec;
202     }
203     /* Initializing status structure */
204     status->flags = 0;
205     status->base  = 0;
206     status->current  = 0;
207     status->width = 0;
208     status->prec  = 0;
209
210     /* First come 0..n flags */
211     do
212     {
213         switch ( *spec )
214         {
215             case '-':
216                 /* left-aligned output */
217                 status->flags |= E_minus;
218                 ++spec;
219                 break;
220             case '+':
221                 /* positive numbers prefixed with '+' */
222                 status->flags |= E_plus;
223                 ++spec;
224                 break;
225             case '#':
226                 /* alternative format (leading 0x for hex, 0 for octal) */
227                 status->flags |= E_alt;
228                 ++spec;
229                 break;
230             case ' ':
231                 /* positive numbers prefixed with ' ' */
232                 status->flags |= E_space;
233                 ++spec;
234                 break;
235             case '0':
236                 /* right-aligned padding done with '0' instead of ' ' */
237                 status->flags |= E_zero;
238                 ++spec;
239                 break;
240             default:
241                 /* not a flag, exit flag parsing */
242                 status->flags |= E_done;
243                 break;
244         }
245     } while ( ! ( status->flags & E_done ) );
246
247     /* Optional field width */
248     if ( *spec == '*' )
249     {
250         /* Retrieve width value from argument stack */
251         int width = va_arg( status->arg, int );
252         if ( width < 0 )
253         {
254             status->flags |= E_minus;
255             status->width = abs( width );
256         }
257         else
258         {
259             status->width = width;
260         }
261         ++spec;
262     }
263     else
264     {
265         /* If a width is given, strtol() will return its value. If not given,
266            strtol() will return zero. In both cases, endptr will point to the
267            rest of the conversion specifier - just what we need.
268         */
269         status->width = (int)strtol( spec, (char**)&spec, 10 );
270     }
271
272     /* Optional precision */
273     if ( *spec == '.' )
274     {
275         ++spec;
276         if ( *spec == '*' )
277         {
278             /* Retrieve precision value from argument stack. A negative value
279                is as if no precision is given - as precision is initalized to
280                EOF (negative), there is no need for testing for negative here.
281             */
282             status->prec = va_arg( status->arg, int );
283         }
284         else
285         {
286             char * endptr;
287             status->prec = (int)strtol( spec, &endptr, 10 );
288             if ( spec == endptr )
289             {
290                 /* Decimal point but no number - bad conversion specifier. */
291                 return orig_spec;
292             }
293             spec = endptr;
294         }
295         /* Having a precision cancels out any zero flag. */
296         status->flags ^= E_zero;
297     }
298
299     /* Optional length modifier
300        We step one character ahead in any case, and step back only if we find
301        there has been no length modifier (or step ahead another character if it
302        has been "hh" or "ll").
303     */
304     switch ( *(spec++) )
305     {
306         case 'h':
307             if ( *spec == 'h' )
308             {
309                 /* hh -> char */
310                 status->flags |= E_char;
311                 ++spec;
312             }
313             else
314             {
315                 /* h -> short */
316                 status->flags |= E_short;
317             }
318             break;
319         case 'l':
320             if ( *spec == 'l' )
321             {
322                 /* ll -> long long */
323                 status->flags |= E_llong;
324                 ++spec;
325             }
326             else
327             {
328                 /* k -> long */
329                 status->flags |= E_long;
330             }
331             break;
332         case 'j':
333             /* j -> intfmt_t, which might or might not be long long */
334             status->flags |= E_intmax;
335             break;
336         case 'z':
337             /* z -> size_t, which might or might not be unsigned int */
338             status->flags |= E_size;
339             break;
340         case 't':
341             /* t -> ptrdiff_t, which might or might not be long */
342             status->flags |= E_ptrdiff;
343             break;
344         case 'L':
345             /* L -> long double */
346             status->flags |= E_ldouble;
347             break;
348         default:
349             --spec;
350             break;
351     }
352
353     /* Conversion specifier */
354     switch ( *spec )
355     {
356         case 'd':
357             /* FALLTHROUGH */
358         case 'i':
359             status->base = 10;
360             break;
361         case 'o':
362             status->base = 8;
363             status->flags |= E_unsigned;
364             break;
365         case 'u':
366             status->base = 10;
367             status->flags |= E_unsigned;
368             break;
369         case 'x':
370             status->base = 16;
371             status->flags |= ( E_lower | E_unsigned );
372             break;
373         case 'X':
374             status->base = 16;
375             status->flags |= E_unsigned;
376             break;
377         case 'f':
378         case 'F':
379         case 'e':
380         case 'E':
381         case 'g':
382         case 'G':
383             break;
384         case 'a':
385         case 'A':
386             break;
387         case 'c':
388             /* TODO: Flags, wide chars. */
389             PUT( va_arg( status->arg, int ) );
390             return ++spec;
391         case 's':
392             /* TODO: Flags, wide chars. */
393             {
394                 char * s = va_arg( status->arg, char * );
395                 while ( *s != '\0' )
396                 {
397                     PUT( *(s++) );
398                 }
399                 return ++spec;
400             }
401         case 'p':
402             /* TODO: E_long -> E_intptr */
403             status->base = 16;
404             status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
405             break;
406         case 'n':
407            {
408                int * val = va_arg( status->arg, int * );
409                *val = status->i;
410                return ++spec;
411            }
412         default:
413             /* No conversion specifier. Bad conversion. */
414             return orig_spec;
415     }
416
417     /* Do the actual output based on our findings */
418     if ( status->base != 0 )
419     {
420         /* Integer conversions */
421         /* TODO: Check for invalid flag combinations. */
422         if ( status->flags & E_unsigned )
423         {
424             uintfmt_t value;
425             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
426             {
427                 case E_char:
428                     value = (uintfmt_t)(unsigned char)va_arg( status->arg, int );
429                     break;
430                 case E_short:
431                     value = (uintfmt_t)(unsigned short)va_arg( status->arg, int );
432                     break;
433                 case 0:
434                     value = (uintfmt_t)va_arg( status->arg, unsigned int );
435                     break;
436                 case E_long:
437                     value = (uintfmt_t)va_arg( status->arg, unsigned long );
438                     break;
439                 case E_llong:
440                     value = (uintfmt_t)va_arg( status->arg, unsigned long long );
441                     break;
442                 case E_size:
443                     value = (uintfmt_t)va_arg( status->arg, size_t );
444                     break;
445             }
446             ++(status->current);
447             /* FIXME: The if clause means one-digit values do not get formatted */
448             /* Was introduced originally to get value to "safe" levels re. uintfmt_t. */
449             if ( ( value / status->base ) != 0 )
450             {
451                 int2base( (intfmt_t)(value / status->base), status );
452             }
453             else
454             {
455                 intformat( (intfmt_t)value, status );
456             }
457             int digit = value % status->base;
458             if ( digit < 0 )
459             {
460                 digit *= -1;
461             }
462             if ( status->flags & E_lower )
463             {
464                 PUT( _PDCLIB_digits[ digit ] );
465             }
466             else
467             {
468                 PUT( _PDCLIB_Xdigits[ digit ] );
469             }
470         }
471         else
472         {
473             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
474             {
475                 case E_char:
476                     int2base( (intfmt_t)(char)va_arg( status->arg, int ), status );
477                     break;
478                 case E_short:
479                     int2base( (intfmt_t)(short)va_arg( status->arg, int ), status );
480                     break;
481                 case 0:
482                     int2base( (intfmt_t)va_arg( status->arg, int ), status );
483                     break;
484                 case E_long:
485                     int2base( (intfmt_t)va_arg( status->arg, long ), status );
486                     break;
487                 case E_llong:
488                     int2base( (intfmt_t)va_arg( status->arg, long long ), status );
489                     break;
490                 case E_ptrdiff:
491                     int2base( (intfmt_t)va_arg( status->arg, ptrdiff_t ), status );
492                     break;
493                 case E_intmax:
494                     int2base( va_arg( status->arg, intfmt_t ), status );
495                     break;
496             }
497         }
498         if ( status->flags & E_minus )
499         {
500             while ( status->current < status->width )
501             {
502                 PUT( ' ' );
503                 ++(status->current);
504             }
505         }
506         if ( status->i >= status->n && status->n > 0 )
507         {
508             status->s[status->n - 1] = '\0';
509         }
510     }
511     return ++spec;
512 }
513
514 #ifdef TEST
515 #define _PDCLIB_FILEID "_PDCLIB/print.c"
516 #define _PDCLIB_STRINGIO
517
518 #include <_PDCLIB_test.h>
519
520 static int testprintf( char * buffer, const char * format, ... )
521 {
522     /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
523     struct _PDCLIB_status_t status;
524     status.base = 0;
525     status.flags = 0;
526     status.n = 100;
527     status.i = 0;
528     status.current = 0;
529     status.s = buffer;
530     status.width = 0;
531     status.prec = 0;
532     status.stream = NULL;
533     va_start( status.arg, format );
534     memset( buffer, '\0', 100 );
535     if ( *(_PDCLIB_print( format, &status )) != '\0' )
536     {
537         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
538         ++TEST_RESULTS;
539     }
540     va_end( status.arg );
541     return status.i;
542 }
543
544 #define TEST_CONVERSION_ONLY
545
546 int main( void )
547 {
548     char target[100];
549 #include "printf_testcases.h"
550     return TEST_RESULTS;
551 }
552
553 #endif