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