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