]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/print.c
%s and %c handling.
[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     /* Registering the character being printed at the end of the function here
163        already so it will be taken into account when the deepestmost recursion
164        does the prefix / padding stuff.
165     */
166     ++(status->current);
167     if ( ( value / status->base ) != 0 )
168     {
169         /* More digits to be done - recurse deeper */
170         int2base( value / status->base, status );
171     }
172     else
173     {
174         /* We reached the last digit, the deepest point of our recursion, and
175            only now know how long the number to be printed actually is. Now we
176            have to do the sign, prefix, width, and precision padding stuff
177            before printing the numbers while we resurface from the recursion.
178         */
179         intformat( value, status );
180     }
181     /* Recursion tail - print the current digit. */
182     {
183     int digit = value % status->base;
184     if ( digit < 0 )
185     {
186         digit *= -1;
187     }
188     if ( status->flags & E_lower )
189     {
190         /* Lowercase letters. Same array used for strto...(). */
191         PUT( _PDCLIB_digits[ digit ] );
192     }
193     else
194     {
195         /* Uppercase letters. Array only used here, only 0-F. */
196         PUT( _PDCLIB_Xdigits[ digit ] );
197     }
198     }
199 }
200
201
202 static void stringformat( const char * s, struct _PDCLIB_status_t * status )
203 {
204     if ( status->flags & E_char )
205     {
206         status->prec = 1;
207     }
208     else
209     {
210         if ( status->prec < 0 )
211         {
212             status->prec = strlen( s );
213         }
214         else
215         {
216             for ( int i = 0; i < status->prec; ++i )
217             {
218                 if ( s[i] == 0 )
219                 {
220                     status->prec = i;
221                     break;
222                 }
223             }
224         }
225     }
226     if ( ! ( status->flags & E_minus ) && ( status->width > (_PDCLIB_size_t)status->prec ) )
227     {
228         while ( status->current < ( status->width - status->prec ) )
229         {
230             PUT( ' ' );
231             ++(status->current);
232         }
233     }
234     while ( status->prec > 0 )
235     {
236         PUT( *(s++) );
237         --(status->prec);
238         ++(status->current);
239     }
240     if ( status->flags & E_minus )
241     {
242         while ( status->width > status->current )
243         {
244             PUT( ' ' );
245             ++(status->current);
246         }
247     }
248 }
249
250
251 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
252 {
253     const char * orig_spec = spec;
254     if ( *(++spec) == '%' )
255     {
256         /* %% -> print single '%' */
257         PUT( *spec );
258         return ++spec;
259     }
260     /* Initializing status structure */
261     status->flags = 0;
262     status->base  = 0;
263     status->current  = 0;
264     status->width = 0;
265     status->prec  = EOF;
266
267     /* First come 0..n flags */
268     do
269     {
270         switch ( *spec )
271         {
272             case '-':
273                 /* left-aligned output */
274                 status->flags |= E_minus;
275                 ++spec;
276                 break;
277             case '+':
278                 /* positive numbers prefixed with '+' */
279                 status->flags |= E_plus;
280                 ++spec;
281                 break;
282             case '#':
283                 /* alternative format (leading 0x for hex, 0 for octal) */
284                 status->flags |= E_alt;
285                 ++spec;
286                 break;
287             case ' ':
288                 /* positive numbers prefixed with ' ' */
289                 status->flags |= E_space;
290                 ++spec;
291                 break;
292             case '0':
293                 /* right-aligned padding done with '0' instead of ' ' */
294                 status->flags |= E_zero;
295                 ++spec;
296                 break;
297             default:
298                 /* not a flag, exit flag parsing */
299                 status->flags |= E_done;
300                 break;
301         }
302     } while ( ! ( status->flags & E_done ) );
303
304     /* Optional field width */
305     if ( *spec == '*' )
306     {
307         /* Retrieve width value from argument stack */
308         int width = va_arg( status->arg, int );
309         if ( width < 0 )
310         {
311             status->flags |= E_minus;
312             status->width = abs( width );
313         }
314         else
315         {
316             status->width = width;
317         }
318         ++spec;
319     }
320     else
321     {
322         /* If a width is given, strtol() will return its value. If not given,
323            strtol() will return zero. In both cases, endptr will point to the
324            rest of the conversion specifier - just what we need.
325         */
326         status->width = (int)strtol( spec, (char**)&spec, 10 );
327     }
328
329     /* Optional precision */
330     if ( *spec == '.' )
331     {
332         ++spec;
333         if ( *spec == '*' )
334         {
335             /* Retrieve precision value from argument stack. A negative value
336                is as if no precision is given - as precision is initalized to
337                EOF (negative), there is no need for testing for negative here.
338             */
339             status->prec = va_arg( status->arg, int );
340             ++spec;
341         }
342         else
343         {
344             char * endptr;
345             status->prec = (int)strtol( spec, &endptr, 10 );
346             if ( spec == endptr )
347             {
348                 /* Decimal point but no number - equals zero */
349                 status->prec = 0;
350             }
351             spec = endptr;
352         }
353         /* Having a precision cancels out any zero flag. */
354         status->flags &= ~E_zero;
355     }
356
357     /* Optional length modifier
358        We step one character ahead in any case, and step back only if we find
359        there has been no length modifier (or step ahead another character if it
360        has been "hh" or "ll").
361     */
362     switch ( *(spec++) )
363     {
364         case 'h':
365             if ( *spec == 'h' )
366             {
367                 /* hh -> char */
368                 status->flags |= E_char;
369                 ++spec;
370             }
371             else
372             {
373                 /* h -> short */
374                 status->flags |= E_short;
375             }
376             break;
377         case 'l':
378             if ( *spec == 'l' )
379             {
380                 /* ll -> long long */
381                 status->flags |= E_llong;
382                 ++spec;
383             }
384             else
385             {
386                 /* k -> long */
387                 status->flags |= E_long;
388             }
389             break;
390         case 'j':
391             /* j -> intmax_t, which might or might not be long long */
392             status->flags |= E_intmax;
393             break;
394         case 'z':
395             /* z -> size_t, which might or might not be unsigned int */
396             status->flags |= E_size;
397             break;
398         case 't':
399             /* t -> ptrdiff_t, which might or might not be long */
400             status->flags |= E_ptrdiff;
401             break;
402         case 'L':
403             /* L -> long double */
404             status->flags |= E_ldouble;
405             break;
406         default:
407             --spec;
408             break;
409     }
410
411     /* Conversion specifier */
412     switch ( *spec )
413     {
414         case 'd':
415             /* FALLTHROUGH */
416         case 'i':
417             status->base = 10;
418             break;
419         case 'o':
420             status->base = 8;
421             status->flags |= E_unsigned;
422             break;
423         case 'u':
424             status->base = 10;
425             status->flags |= E_unsigned;
426             break;
427         case 'x':
428             status->base = 16;
429             status->flags |= ( E_lower | E_unsigned );
430             break;
431         case 'X':
432             status->base = 16;
433             status->flags |= E_unsigned;
434             break;
435         case 'f':
436         case 'F':
437         case 'e':
438         case 'E':
439         case 'g':
440         case 'G':
441             break;
442         case 'a':
443         case 'A':
444             break;
445         case 'c':
446             /* TODO: wide chars. */
447             {
448                 char c[1];
449                 c[0] = (char)va_arg( status->arg, int );
450                 status->flags |= E_char;
451                 stringformat( c, status );
452                 return ++spec;
453             }
454         case 's':
455             /* TODO: wide chars. */
456             stringformat( va_arg( status->arg, char * ), status );
457             return ++spec;
458         case 'p':
459             status->base = 16;
460             status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
461             break;
462         case 'n':
463            {
464                int * val = va_arg( status->arg, int * );
465                *val = status->i;
466                return ++spec;
467            }
468         default:
469             /* No conversion specifier. Bad conversion. */
470             return orig_spec;
471     }
472
473     /* Do the actual output based on our findings */
474     if ( status->base != 0 )
475     {
476         /* Integer conversions */
477         /* TODO: Check for invalid flag combinations. */
478         if ( status->flags & E_unsigned )
479         {
480             uintmax_t value;
481             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
482             {
483                 case E_char:
484                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
485                     break;
486                 case E_short:
487                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
488                     break;
489                 case 0:
490                     value = (uintmax_t)va_arg( status->arg, unsigned int );
491                     break;
492                 case E_long:
493                     value = (uintmax_t)va_arg( status->arg, unsigned long );
494                     break;
495                 case E_llong:
496                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
497                     break;
498                 case E_size:
499                     value = (uintmax_t)va_arg( status->arg, size_t );
500                     break;
501                 case E_pointer:
502                     value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
503                     break;
504                 default:
505                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
506                     return NULL;
507             }
508             ++(status->current);
509             if ( ( value / status->base ) != 0 )
510             {
511                 /* Get value to "safe" levels re. uintmax_t. */
512                 int2base( (intmax_t)(value / status->base), status );
513             }
514             else
515             {
516                 intformat( (intmax_t)value, status );
517             }
518             int digit = value % status->base;
519             if ( digit < 0 )
520             {
521                 digit *= -1;
522             }
523             if ( status->flags & E_lower )
524             {
525                 PUT( _PDCLIB_digits[ digit ] );
526             }
527             else
528             {
529                 PUT( _PDCLIB_Xdigits[ digit ] );
530             }
531         }
532         else
533         {
534             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
535             {
536                 case E_char:
537                     int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
538                     break;
539                 case E_short:
540                     int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
541                     break;
542                 case 0:
543                     int2base( (intmax_t)va_arg( status->arg, int ), status );
544                     break;
545                 case E_long:
546                     int2base( (intmax_t)va_arg( status->arg, long ), status );
547                     break;
548                 case E_llong:
549                     int2base( (intmax_t)va_arg( status->arg, long long ), status );
550                     break;
551                 case E_ptrdiff:
552                     int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
553                     break;
554                 case E_intmax:
555                     int2base( va_arg( status->arg, intmax_t ), status );
556                     break;
557                 default:
558                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
559                     return NULL;
560             }
561         }
562         if ( status->flags & E_minus )
563         {
564             while ( status->current < status->width )
565             {
566                 PUT( ' ' );
567                 ++(status->current);
568             }
569         }
570         if ( status->i >= status->n && status->n > 0 )
571         {
572             status->s[status->n - 1] = '\0';
573         }
574     }
575     return ++spec;
576 }
577
578 #endif
579
580 #ifdef TEST
581 #define _PDCLIB_FILEID "_PDCLIB/print.c"
582 #define _PDCLIB_STRINGIO
583
584 #include "_PDCLIB_test.h"
585
586 #ifndef REGTEST
587
588 static int testprintf( char * buffer, const char * format, ... )
589 {
590     /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
591     struct _PDCLIB_status_t status;
592     status.base = 0;
593     status.flags = 0;
594     status.n = 100;
595     status.i = 0;
596     status.current = 0;
597     status.s = buffer;
598     status.width = 0;
599     status.prec = 0;
600     status.stream = NULL;
601     va_start( status.arg, format );
602     memset( buffer, '\0', 100 );
603     if ( *(_PDCLIB_print( format, &status )) != '\0' )
604     {
605         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
606         ++TEST_RESULTS;
607     }
608     va_end( status.arg );
609     return status.i;
610 }
611
612 #endif
613
614 #define TEST_CONVERSION_ONLY
615
616 int main( void )
617 {
618 #ifndef REGTEST
619     char target[100];
620 #include "printf_testcases.h"
621 #endif
622     return TEST_RESULTS;
623 }
624
625 #endif