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