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