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