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