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