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