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