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