]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/print.c
d2b5dbddc41c29dc895d4e3d17621df31c37fd05
[pdclib] / functions / _PDCLIB / print.c
1 /* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )
2
3    This file is part of the Public Domain C Library (PDCLib).
4    Permission is granted to use, modify, and / or redistribute at will.
5 */
6
7 #include <stdio.h>
8 #include <stdint.h>
9 #include <stdarg.h>
10 #include <stdlib.h>
11 #include <stddef.h>
12
13 #ifndef REGTEST
14
15 /* Using an integer's bits as flags for both the conversion flags and length
16    modifiers.
17 */
18 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
19           width flags) into a combined field.
20 */
21 #define E_minus    (1<<0)
22 #define E_plus     (1<<1)
23 #define E_alt      (1<<2)
24 #define E_space    (1<<3)
25 #define E_zero     (1<<4)
26 #define E_done     (1<<5)
27
28 #define E_char     (1<<6)
29 #define E_short    (1<<7)
30 #define E_long     (1<<8)
31 #define E_llong    (1<<9)
32 #define E_intmax   (1<<10)
33 #define E_size     (1<<11)
34 #define E_ptrdiff  (1<<12)
35 #define E_pointer  (1<<13)
36
37 #define E_ldouble  (1<<14)
38
39 #define E_lower    (1<<15)
40 #define E_unsigned (1<<16)
41
42 /* This macro delivers a given character to either a memory buffer or a stream,
43    depending on the contents of 'status' (struct _PDCLIB_status_t).
44    x - the character to be delivered
45    i - pointer to number of characters already delivered in this call
46    n - pointer to maximum number of characters to be delivered in this call
47    s - the buffer into which the character shall be delivered
48 */
49 #define PUT( x ) \
50 do { \
51     int character = x; \
52     if ( status->i < status->n ) { \
53         if ( status->stream != NULL ) \
54             putc( character, status->stream ); \
55         else \
56             status->s[status->i] = character; \
57     } \
58     ++(status->i); \
59 } while ( 0 )
60
61
62 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
63 {
64     if ( status->prec < 0 )
65     {
66         status->prec = 1;
67     }
68     /* At worst, we need two prefix characters (hex prefix). */
69     char preface[3] = "\0";
70     size_t preidx = 0;
71     if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) && ( value != 0 ) )
72     {
73         /* Octal / hexadecimal prefix for "%#" conversions */
74         preface[ preidx++ ] = '0';
75         if ( status->base == 16 )
76         {
77             preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
78         }
79     }
80     if ( value < 0 )
81     {
82         /* Negative sign for negative values - at all times. */
83         preface[ preidx++ ] = '-';
84     }
85     else if ( ! ( status->flags & E_unsigned ) )
86     {
87         /* plus sign / extra space are only for unsigned conversions */
88         if ( status->flags & E_plus )
89         {
90             preface[ preidx++ ] = '+';
91         }
92         else if ( status->flags & E_space )
93         {
94             preface[ preidx++ ] = ' ';
95         }
96     }
97     {
98     /* At this point, status->current has the number of digits queued up.
99        Determine if we have a precision requirement to pad those.
100     */
101     size_t prec_pads = ( (_PDCLIB_size_t)status->prec > status->current ) ? ( (_PDCLIB_size_t)status->prec - status->current ) : 0;
102     if ( ! ( status->flags & ( E_minus | E_zero ) ) )
103     {
104         /* Space padding is only done if no zero padding or left alignment
105            is requested. Calculate the number of characters that WILL be
106            printed, including any prefixes determined above.
107         */
108         /* The number of characters to be printed, plus prefixes if any. */
109         /* This line contained probably the most stupid, time-wasting bug
110            I've ever perpetrated. Greetings to Samface, DevL, and all
111            sceners at Breakpoint 2006.
112         */
113         size_t characters = preidx + ( ( status->current > (_PDCLIB_size_t)status->prec ) ? status->current : (_PDCLIB_size_t)status->prec );
114         if ( status->width > characters )
115         {
116             for ( size_t i = 0; i < status->width - characters; ++i )
117             {
118                 PUT( ' ' );
119                 ++(status->current);
120             }
121         }
122     }
123     /* Now we did the padding, do the prefixes (if any). */
124     preidx = 0;
125     while ( preface[ preidx ] != '\0' )
126     {
127         PUT( preface[ preidx++ ] );
128         ++(status->current);
129     }
130     /* Do the precision padding if necessary. */
131     while ( prec_pads-- > 0 )
132     {
133         PUT( '0' );
134         ++(status->current);
135     }
136     if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
137     {
138         /* If field is not left aligned, and zero padding is requested, do
139            so.
140         */
141         while ( status->current < status->width )
142         {
143             PUT( '0' );
144             ++(status->current);
145         }
146     }
147     }
148 }
149
150
151 /* This function recursively converts a given integer value to a character
152    stream. The conversion is done under the control of a given status struct
153    and written either to a character string or a stream, depending on that
154    same status struct. The status struct also keeps the function from exceeding
155    snprintf() limits, and enables any necessary padding / prefixing of the
156    output once the number of characters to be printed is known, which happens
157    at the lowermost recursion level.
158 */
159 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
160 {
161     /* Registering the character being printed at the end of the function here
162        already so it will be taken into account when the deepestmost recursion
163        does the prefix / padding stuff.
164     */
165     ++(status->current);
166     if ( ( value / status->base ) != 0 )
167     {
168         /* More digits to be done - recurse deeper */
169         int2base( value / status->base, status );
170     }
171     else
172     {
173         /* We reached the last digit, the deepest point of our recursion, and
174            only now know how long the number to be printed actually is. Now we
175            have to do the sign, prefix, width, and precision padding stuff
176            before printing the numbers while we resurface from the recursion.
177         */
178         intformat( value, status );
179     }
180     /* Recursion tail - print the current digit. */
181     {
182     int digit = value % status->base;
183     if ( digit < 0 )
184     {
185         digit *= -1;
186     }
187     if ( status->flags & E_lower )
188     {
189         /* Lowercase letters. Same array used for strto...(). */
190         PUT( _PDCLIB_digits[ digit ] );
191     }
192     else
193     {
194         /* Uppercase letters. Array only used here, only 0-F. */
195         PUT( _PDCLIB_Xdigits[ digit ] );
196     }
197     }
198 }
199
200
201 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
202 {
203     const char * orig_spec = spec;
204     if ( *(++spec) == '%' )
205     {
206         /* %% -> print single '%' */
207         PUT( *spec );
208         return ++spec;
209     }
210     /* Initializing status structure */
211     status->flags = 0;
212     status->base  = 0;
213     status->current  = 0;
214     status->width = 0;
215     status->prec  = EOF;
216
217     /* First come 0..n flags */
218     do
219     {
220         switch ( *spec )
221         {
222             case '-':
223                 /* left-aligned output */
224                 status->flags |= E_minus;
225                 ++spec;
226                 break;
227             case '+':
228                 /* positive numbers prefixed with '+' */
229                 status->flags |= E_plus;
230                 ++spec;
231                 break;
232             case '#':
233                 /* alternative format (leading 0x for hex, 0 for octal) */
234                 status->flags |= E_alt;
235                 ++spec;
236                 break;
237             case ' ':
238                 /* positive numbers prefixed with ' ' */
239                 status->flags |= E_space;
240                 ++spec;
241                 break;
242             case '0':
243                 /* right-aligned padding done with '0' instead of ' ' */
244                 status->flags |= E_zero;
245                 ++spec;
246                 break;
247             default:
248                 /* not a flag, exit flag parsing */
249                 status->flags |= E_done;
250                 break;
251         }
252     } while ( ! ( status->flags & E_done ) );
253
254     /* Optional field width */
255     if ( *spec == '*' )
256     {
257         /* Retrieve width value from argument stack */
258         int width = va_arg( status->arg, int );
259         if ( width < 0 )
260         {
261             status->flags |= E_minus;
262             status->width = abs( width );
263         }
264         else
265         {
266             status->width = width;
267         }
268         ++spec;
269     }
270     else
271     {
272         /* If a width is given, strtol() will return its value. If not given,
273            strtol() will return zero. In both cases, endptr will point to the
274            rest of the conversion specifier - just what we need.
275         */
276         status->width = (int)strtol( spec, (char**)&spec, 10 );
277     }
278
279     /* Optional precision */
280     if ( *spec == '.' )
281     {
282         ++spec;
283         if ( *spec == '*' )
284         {
285             /* Retrieve precision value from argument stack. A negative value
286                is as if no precision is given - as precision is initalized to
287                EOF (negative), there is no need for testing for negative here.
288             */
289             status->prec = va_arg( status->arg, int );
290             ++spec;
291         }
292         else
293         {
294             char * endptr;
295             status->prec = (int)strtol( spec, &endptr, 10 );
296             if ( spec == endptr )
297             {
298                 /* Decimal point but no number - equals zero */
299                 status->prec = 0;
300             }
301             spec = endptr;
302         }
303         /* Having a precision cancels out any zero flag. */
304         status->flags &= ~E_zero;
305     }
306
307     /* Optional length modifier
308        We step one character ahead in any case, and step back only if we find
309        there has been no length modifier (or step ahead another character if it
310        has been "hh" or "ll").
311     */
312     switch ( *(spec++) )
313     {
314         case 'h':
315             if ( *spec == 'h' )
316             {
317                 /* hh -> char */
318                 status->flags |= E_char;
319                 ++spec;
320             }
321             else
322             {
323                 /* h -> short */
324                 status->flags |= E_short;
325             }
326             break;
327         case 'l':
328             if ( *spec == 'l' )
329             {
330                 /* ll -> long long */
331                 status->flags |= E_llong;
332                 ++spec;
333             }
334             else
335             {
336                 /* k -> long */
337                 status->flags |= E_long;
338             }
339             break;
340         case 'j':
341             /* j -> intmax_t, which might or might not be long long */
342             status->flags |= E_intmax;
343             break;
344         case 'z':
345             /* z -> size_t, which might or might not be unsigned int */
346             status->flags |= E_size;
347             break;
348         case 't':
349             /* t -> ptrdiff_t, which might or might not be long */
350             status->flags |= E_ptrdiff;
351             break;
352         case 'L':
353             /* L -> long double */
354             status->flags |= E_ldouble;
355             break;
356         default:
357             --spec;
358             break;
359     }
360
361     /* Conversion specifier */
362     switch ( *spec )
363     {
364         case 'd':
365             /* FALLTHROUGH */
366         case 'i':
367             status->base = 10;
368             break;
369         case 'o':
370             status->base = 8;
371             status->flags |= E_unsigned;
372             break;
373         case 'u':
374             status->base = 10;
375             status->flags |= E_unsigned;
376             break;
377         case 'x':
378             status->base = 16;
379             status->flags |= ( E_lower | E_unsigned );
380             break;
381         case 'X':
382             status->base = 16;
383             status->flags |= E_unsigned;
384             break;
385         case 'f':
386         case 'F':
387         case 'e':
388         case 'E':
389         case 'g':
390         case 'G':
391             break;
392         case 'a':
393         case 'A':
394             break;
395         case 'c':
396             /* TODO: Flags, wide chars. */
397             PUT( va_arg( status->arg, int ) );
398             return ++spec;
399         case 's':
400             /* TODO: Flags, wide chars. */
401             {
402                 char * s = va_arg( status->arg, char * );
403                 while ( *s != '\0' )
404                 {
405                     PUT( *(s++) );
406                 }
407                 return ++spec;
408             }
409         case 'p':
410             /* TODO: E_long -> E_intptr */
411             status->base = 16;
412             status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
413             break;
414         case 'n':
415            {
416                int * val = va_arg( status->arg, int * );
417                *val = status->i;
418                return ++spec;
419            }
420         default:
421             /* No conversion specifier. Bad conversion. */
422             return orig_spec;
423     }
424
425     /* Do the actual output based on our findings */
426     if ( status->base != 0 )
427     {
428         /* Integer conversions */
429         /* TODO: Check for invalid flag combinations. */
430         if ( status->flags & E_unsigned )
431         {
432             uintmax_t value;
433             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
434             {
435                 case E_char:
436                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
437                     break;
438                 case E_short:
439                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
440                     break;
441                 case 0:
442                     value = (uintmax_t)va_arg( status->arg, unsigned int );
443                     break;
444                 case E_long:
445                     value = (uintmax_t)va_arg( status->arg, unsigned long );
446                     break;
447                 case E_llong:
448                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
449                     break;
450                 case E_size:
451                     value = (uintmax_t)va_arg( status->arg, size_t );
452                     break;
453                 case E_pointer:
454                     value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
455                     break;
456                 default:
457                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
458                     return NULL;
459             }
460             ++(status->current);
461             if ( ( value / status->base ) != 0 )
462             {
463                 /* Get value to "safe" levels re. uintmax_t. */
464                 int2base( (intmax_t)(value / status->base), status );
465             }
466             else
467             {
468                 intformat( (intmax_t)value, status );
469             }
470             int digit = value % status->base;
471             if ( digit < 0 )
472             {
473                 digit *= -1;
474             }
475             if ( status->flags & E_lower )
476             {
477                 PUT( _PDCLIB_digits[ digit ] );
478             }
479             else
480             {
481                 PUT( _PDCLIB_Xdigits[ digit ] );
482             }
483         }
484         else
485         {
486             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
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_ptrdiff:
504                     int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
505                     break;
506                 case E_intmax:
507                     int2base( va_arg( status->arg, intmax_t ), status );
508                     break;
509                 default:
510                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
511                     return NULL;
512             }
513         }
514         if ( status->flags & E_minus )
515         {
516             while ( status->current < status->width )
517             {
518                 PUT( ' ' );
519                 ++(status->current);
520             }
521         }
522         if ( status->i >= status->n && status->n > 0 )
523         {
524             status->s[status->n - 1] = '\0';
525         }
526     }
527     return ++spec;
528 }
529
530 #endif
531
532 #ifdef TEST
533 #define _PDCLIB_FILEID "_PDCLIB/print.c"
534 #define _PDCLIB_STRINGIO
535
536 #include "_PDCLIB_test.h"
537
538 #ifndef REGTEST
539
540 static int testprintf( char * buffer, const char * format, ... )
541 {
542     /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
543     struct _PDCLIB_status_t status;
544     status.base = 0;
545     status.flags = 0;
546     status.n = 100;
547     status.i = 0;
548     status.current = 0;
549     status.s = buffer;
550     status.width = 0;
551     status.prec = 0;
552     status.stream = NULL;
553     va_start( status.arg, format );
554     memset( buffer, '\0', 100 );
555     if ( *(_PDCLIB_print( format, &status )) != '\0' )
556     {
557         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
558         ++TEST_RESULTS;
559     }
560     va_end( status.arg );
561     return status.i;
562 }
563
564 #endif
565
566 #define TEST_CONVERSION_ONLY
567
568 int main( void )
569 {
570 #ifndef REGTEST
571     char target[100];
572 #include "printf_testcases.h"
573 #endif
574     return TEST_RESULTS;
575 }
576
577 #endif