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