]> pd.if.org Git - pdclib.old/blob - functions/_PDCLIB/print.c
PDCLIB-6: Inside _PDCLIB_print, remove intformat and completely rewrite int2base...
[pdclib.old] / 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                 written += padding < 2 ? 2 - padding : 0;
144                 outend[-written    ] = '0';
145                 outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';
146                 break;
147             default:
148                 break;
149         }
150     }
151
152     // Space padding to field width
153     if ( ! ( status->flags & ( E_minus | E_zero ) ) )
154     {
155         while( written < status->width ) outend[-++written] = ' ';
156     }
157
158     // Write output
159     status->current = written;
160     while ( written )
161         PUT( outend[-written--] );
162 }
163
164 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
165 {
166     const char * orig_spec = spec;
167     if ( *(++spec) == '%' )
168     {
169         /* %% -> print single '%' */
170         PUT( *spec );
171         return ++spec;
172     }
173     /* Initializing status structure */
174     status->flags = 0;
175     status->base  = 0;
176     status->current  = 0;
177     status->width = 0;
178     status->prec  = 0;
179
180     /* First come 0..n flags */
181     do
182     {
183         switch ( *spec )
184         {
185             case '-':
186                 /* left-aligned output */
187                 status->flags |= E_minus;
188                 ++spec;
189                 break;
190             case '+':
191                 /* positive numbers prefixed with '+' */
192                 status->flags |= E_plus;
193                 ++spec;
194                 break;
195             case '#':
196                 /* alternative format (leading 0x for hex, 0 for octal) */
197                 status->flags |= E_alt;
198                 ++spec;
199                 break;
200             case ' ':
201                 /* positive numbers prefixed with ' ' */
202                 status->flags |= E_space;
203                 ++spec;
204                 break;
205             case '0':
206                 /* right-aligned padding done with '0' instead of ' ' */
207                 status->flags |= E_zero;
208                 ++spec;
209                 break;
210             default:
211                 /* not a flag, exit flag parsing */
212                 status->flags |= E_done;
213                 break;
214         }
215     } while ( ! ( status->flags & E_done ) );
216
217     /* Optional field width */
218     if ( *spec == '*' )
219     {
220         /* Retrieve width value from argument stack */
221         int width = va_arg( status->arg, int );
222         if ( width < 0 )
223         {
224             status->flags |= E_minus;
225             status->width = abs( width );
226         }
227         else
228         {
229             status->width = width;
230         }
231         ++spec;
232     }
233     else
234     {
235         /* If a width is given, strtol() will return its value. If not given,
236            strtol() will return zero. In both cases, endptr will point to the
237            rest of the conversion specifier - just what we need.
238         */
239         status->width = (int)strtol( spec, (char**)&spec, 10 );
240     }
241
242     /* Optional precision */
243     if ( *spec == '.' )
244     {
245         ++spec;
246         if ( *spec == '*' )
247         {
248             /* Retrieve precision value from argument stack. A negative value
249                is as if no precision is given - as precision is initalized to
250                EOF (negative), there is no need for testing for negative here.
251             */
252             status->prec = va_arg( status->arg, int );
253         }
254         else
255         {
256             char * endptr;
257             status->prec = (int)strtol( spec, &endptr, 10 );
258             if ( spec == endptr )
259             {
260                 /* Decimal point but no number - bad conversion specifier. */
261                 return orig_spec;
262             }
263             spec = endptr;
264         }
265         /* Having a precision cancels out any zero flag. */
266         status->flags ^= E_zero;
267     }
268
269     /* Optional length modifier
270        We step one character ahead in any case, and step back only if we find
271        there has been no length modifier (or step ahead another character if it
272        has been "hh" or "ll").
273     */
274     switch ( *(spec++) )
275     {
276         case 'h':
277             if ( *spec == 'h' )
278             {
279                 /* hh -> char */
280                 status->flags |= E_char;
281                 ++spec;
282             }
283             else
284             {
285                 /* h -> short */
286                 status->flags |= E_short;
287             }
288             break;
289         case 'l':
290             if ( *spec == 'l' )
291             {
292                 /* ll -> long long */
293                 status->flags |= E_llong;
294                 ++spec;
295             }
296             else
297             {
298                 /* k -> long */
299                 status->flags |= E_long;
300             }
301             break;
302         case 'j':
303             /* j -> intmax_t, which might or might not be long long */
304             status->flags |= E_intmax;
305             break;
306         case 'z':
307             /* z -> size_t, which might or might not be unsigned int */
308             status->flags |= E_size;
309             break;
310         case 't':
311             /* t -> ptrdiff_t, which might or might not be long */
312             status->flags |= E_ptrdiff;
313             break;
314         case 'L':
315             /* L -> long double */
316             status->flags |= E_ldouble;
317             break;
318         default:
319             --spec;
320             break;
321     }
322
323     /* Conversion specifier */
324     switch ( *spec )
325     {
326         case 'd':
327             /* FALLTHROUGH */
328         case 'i':
329             status->base = 10;
330             break;
331         case 'o':
332             status->base = 8;
333             status->flags |= E_unsigned;
334             break;
335         case 'u':
336             status->base = 10;
337             status->flags |= E_unsigned;
338             break;
339         case 'x':
340             status->base = 16;
341             status->flags |= ( E_lower | E_unsigned );
342             break;
343         case 'X':
344             status->base = 16;
345             status->flags |= E_unsigned;
346             break;
347         case 'f':
348         case 'F':
349         case 'e':
350         case 'E':
351         case 'g':
352         case 'G':
353             break;
354         case 'a':
355         case 'A':
356             break;
357         case 'c':
358             /* TODO: Flags, wide chars. */
359             PUT( va_arg( status->arg, int ) );
360             return ++spec;
361         case 's':
362             /* TODO: Flags, wide chars. */
363             {
364                 char * s = va_arg( status->arg, char * );
365                 while ( *s != '\0' )
366                 {
367                     PUT( *(s++) );
368                 }
369                 return ++spec;
370             }
371         case 'p':
372             /* TODO: E_long -> E_intptr */
373             status->base = 16;
374             status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
375             break;
376         case 'n':
377            {
378                int * val = va_arg( status->arg, int * );
379                *val = status->i;
380                return ++spec;
381            }
382         default:
383             /* No conversion specifier. Bad conversion. */
384             return orig_spec;
385     }
386
387     /* Do the actual output based on our findings */
388     if ( status->base != 0 )
389     {
390         /* Integer conversions */
391         /* TODO: Check for invalid flag combinations. */
392         if ( status->flags & E_unsigned )
393         {
394             uintmax_t value;
395             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
396             {
397                 case E_char:
398                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
399                     break;
400                 case E_short:
401                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
402                     break;
403                 case 0:
404                     value = (uintmax_t)va_arg( status->arg, unsigned int );
405                     break;
406                 case E_long:
407                     value = (uintmax_t)va_arg( status->arg, unsigned long );
408                     break;
409                 case E_llong:
410                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
411                     break;
412                 case E_size:
413                     value = (uintmax_t)va_arg( status->arg, size_t );
414                     break;
415             }
416             int2base( value, status );
417         }
418         else
419         {
420             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
421             {
422                 case E_char:
423                     int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
424                     break;
425                 case E_short:
426                     int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
427                     break;
428                 case 0:
429                     int2base( (intmax_t)va_arg( status->arg, int ), status );
430                     break;
431                 case E_long:
432                     int2base( (intmax_t)va_arg( status->arg, long ), status );
433                     break;
434                 case E_llong:
435                     int2base( (intmax_t)va_arg( status->arg, long long ), status );
436                     break;
437                 case E_ptrdiff:
438                     int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
439                     break;
440                 case E_intmax:
441                     int2base( va_arg( status->arg, intmax_t ), status );
442                     break;
443             }
444         }
445         if ( status->flags & E_minus )
446         {
447             while ( status->current < status->width )
448             {
449                 PUT( ' ' );
450                 ++(status->current);
451             }
452         }
453         if ( status->i >= status->n && status->n > 0 )
454         {
455             status->s[status->n - 1] = '\0';
456         }
457     }
458     return ++spec;
459 }
460
461 #endif
462
463 #ifdef TEST
464 #define _PDCLIB_FILEID "_PDCLIB/print.c"
465 #define _PDCLIB_STRINGIO
466
467 #include <_PDCLIB_test.h>
468
469 #ifndef REGTEST
470 static int testprintf( char * buffer, const char * format, ... )
471 {
472     /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
473     struct _PDCLIB_status_t status;
474     status.base = 0;
475     status.flags = 0;
476     status.n = 100;
477     status.i = 0;
478     status.current = 0;
479     status.s = buffer;
480     status.width = 0;
481     status.prec = 0;
482     status.stream = NULL;
483     va_start( status.arg, format );
484     memset( buffer, '\0', 100 );
485     if ( *(_PDCLIB_print( format, &status )) != '\0' )
486     {
487         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
488         ++TEST_RESULTS;
489     }
490     va_end( status.arg );
491     return status.i;
492 }
493 #endif
494
495 #define TEST_CONVERSION_ONLY
496
497 int main( void )
498 {
499 #ifndef REGTEST
500     char target[100];
501 #include "printf_testcases.h"
502 #endif
503     return TEST_RESULTS;
504 }
505
506 #endif