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