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