Add _cbprintf/_vcbprintf (callback based printf formatters)
[pdclib.old] / functions / stdio / _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 <string.h>
13 #include <stdlib.h>
14 #include <stddef.h>
15 #include <stdbool.h>
16 #include <limits.h>
17
18 #ifndef REGTEST
19 #include <_PDCLIB_io.h>
20
21 /* Using an integer's bits as flags for both the conversion flags and length
22    modifiers.
23 */
24 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
25           width flags) into a combined field.
26 */
27 #define E_minus    (1<<0)
28 #define E_plus     (1<<1)
29 #define E_alt      (1<<2)
30 #define E_space    (1<<3)
31 #define E_zero     (1<<4)
32 #define E_done     (1<<5)
33
34 #define E_char     (1<<6)
35 #define E_short    (1<<7)
36 #define E_long     (1<<8)
37 #define E_llong    (1<<9)
38 #define E_intmax   (1<<10)
39 #define E_size     (1<<11)
40 #define E_ptrdiff  (1<<12)
41 #define E_intptr   (1<<13)
42
43 #define E_ldouble  (1<<14)
44
45 #define E_lower    (1<<15)
46 #define E_unsigned (1<<16)
47
48 #define E_TYPES (E_char | E_short | E_long | E_llong | E_intmax \
49                 | E_size | E_ptrdiff | E_intptr)
50
51 /* returns true if callback-based output succeeded; else false */
52 static inline bool cbout(
53     struct _PDCLIB_status_t * status,
54     const void * buf,
55     size_t size )
56 {
57     size_t rv = status->write( status->ctx, buf, size );
58     status->i       += rv;
59     status->current += rv;
60     return rv == size;
61 }
62
63 /* repeated output of a single character */
64 static inline bool cbrept(
65     struct _PDCLIB_status_t * status,
66     char c,
67     size_t times )
68 {
69     if ( sizeof(size_t) == 8 && CHAR_BIT == 8)
70     {
71         uint64_t spread = UINT64_C(0x0101010101010101) * c;
72         while ( times )
73         {
74             size_t n = times > 8 ? 8 : times;
75             if ( !cbout( status, &spread, n ) )
76                 return false;
77             times -= n;
78         }
79         return true;
80     }
81     else if ( sizeof(size_t) == 4  && CHAR_BIT == 8)
82     {
83         uint32_t spread = UINT32_C(0x01010101) * c;
84         while ( times )
85         {
86             size_t n = times > 4 ? 4 : times;
87             if ( !cbout( status, &spread, n ) )
88                 return false;
89             times -= n;
90         }
91         return true;
92     }
93     else
94     {
95         while ( times )
96         {
97             if ( !cbout( status, &c, 1) )
98                 return false;
99             times--;
100         }
101         return true;
102     }
103 }
104
105
106 /* Maximum number of output characters =
107  *   number of bits in (u)intmax_t / number of bits per character in smallest
108  *   base. Smallest base is octal, 3 bits/char.
109  *
110  * Additionally require 2 extra characters for prefixes
111  *
112  * Returns false if an I/O error occured.
113  */
114 static const size_t maxIntLen = sizeof(intmax_t) * CHAR_BIT / 3 + 1;
115
116 static bool int2base( uintmax_t value, struct _PDCLIB_status_t * status )
117 {
118     char sign = 0;
119     if ( ! ( status->flags & E_unsigned ) )
120     {
121         intmax_t signval = (intmax_t) value;
122         bool negative = signval < 0;
123         value = signval < 0 ? -signval : signval;
124
125         if ( negative )
126         {
127             sign = '-';
128         }
129         else if ( status->flags & E_plus )
130         {
131             sign = '+';
132         }
133         else if (status->flags & E_space )
134         {
135             sign = ' ';
136         }
137     }
138
139     // The user could theoretically ask for a silly buffer length here.
140     // Perhaps after a certain size we should malloc? Or do we refuse to protect
141     // them from their own stupidity?
142     size_t bufLen = (status->width > maxIntLen ? status->width : maxIntLen) + 2;
143     char outbuf[bufLen];
144     char * outend = outbuf + bufLen;
145     int written = 0;
146
147     // Build up our output string - backwards
148     {
149         const char * digits = (status->flags & E_lower) ?
150                                 _PDCLIB_digits : _PDCLIB_Xdigits;
151         uintmax_t remaining = value;
152         if(status->prec != 0 || remaining != 0) do {
153             uintmax_t digit = remaining % status->base;
154             remaining /= status->base;
155
156             outend[-++written] = digits[digit];
157         } while(remaining != 0);
158     }
159
160     // Pad field out to the precision specification
161     while( (long) written < status->prec ) outend[-++written] = '0';
162
163     // If a field width specified, and zero padding was requested, then pad to
164     // the field width
165     unsigned padding = 0;
166     if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
167     {
168         while( written < (int) status->width )
169         {
170             outend[-++written] = '0';
171             padding++;
172         }
173     }
174
175     // Prefixes
176     if ( sign != 0 )
177     {
178         if ( padding == 0 ) written++;
179         outend[-written] = sign;
180     }
181     else if ( status->flags & E_alt )
182     {
183         switch ( status->base )
184         {
185             case 8:
186                 if ( outend[-written] != '0' ) outend[-++written] = '0';
187                 break;
188             case 16:
189                 // No prefix if zero
190                 if ( value == 0 ) break;
191
192                 written += padding < 2 ? 2 - padding : 0;
193                 outend[-written    ] = '0';
194                 outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';
195                 break;
196             default:
197                 break;
198         }
199     }
200
201     // Space padding to field width
202     if ( ! ( status->flags & ( E_minus | E_zero ) ) )
203     {
204         while( written < (int) status->width ) outend[-++written] = ' ';
205     }
206
207     // Write output
208     return cbout( status, outend - written, written );
209 }
210
211 /* print a string. returns false if an I/O error occured */
212 static bool printstr( const char * str, struct _PDCLIB_status_t * status )
213 {
214     size_t len = status->prec >= 0 ? strnlen( str, status-> prec)
215                                    : strlen(str);
216
217     if ( status->width == 0 || status->flags & E_minus )
218     {
219         // Simple case or left justification
220         if ( status->prec > 0 )
221         {
222             len = (unsigned) status->prec < len ? (unsigned)  status->prec : len;
223         }
224
225         if ( !cbout( status, str, len ) )
226             return false;
227
228         /* right padding */
229         if ( status->width > status->current ) {
230             len = status->width - status->current;
231
232             if ( !cbrept( status, ' ', len ) )
233                 return false;
234         }
235     } else {
236         // Right justification
237
238         if ( status->width > len ) {
239             size_t padding = status->width - len;
240
241             if ( !cbrept( status, ' ', padding ))
242                 return false;
243         }
244
245         if ( !cbout( status, str, len ) )
246             return false;
247     }
248
249     return true;
250 }
251
252 static bool printchar( char chr, struct _PDCLIB_status_t * status )
253 {
254     if( ! ( status->flags & E_minus ) )
255     {
256         // Right justification
257         if ( status-> width ) {
258             size_t justification = status->width - status->current - 1;
259             if ( !cbrept( status, ' ', justification ))
260                 return false;
261         }
262
263         if ( !cbout( status, &chr, 1 ))
264             return false;
265     } else {
266         // Left justification
267
268         if ( !cbout( status, &chr, 1 ))
269             return false;
270
271         if ( status->width > status->current ) {
272             if ( !cbrept( status, ' ', status->width - status->current ) )
273                 return false;
274         }
275     }
276
277     return true;
278 }
279
280 int _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
281 {
282     const char * orig_spec = spec;
283     if ( *(++spec) == '%' )
284     {
285         /* %% -> print single '%' */
286         if ( !cbout(status, spec, 1) )
287             return -1;
288         ++spec;
289         return (spec - orig_spec);
290     }
291     /* Initializing status structure */
292     status->flags = 0;
293     status->base  = 0;
294     status->current  = 0;
295     status->width = 0;
296     status->prec  = EOF;
297
298     /* First come 0..n flags */
299     do
300     {
301         switch ( *spec )
302         {
303             case '-':
304                 /* left-aligned output */
305                 status->flags |= E_minus;
306                 ++spec;
307                 break;
308             case '+':
309                 /* positive numbers prefixed with '+' */
310                 status->flags |= E_plus;
311                 ++spec;
312                 break;
313             case '#':
314                 /* alternative format (leading 0x for hex, 0 for octal) */
315                 status->flags |= E_alt;
316                 ++spec;
317                 break;
318             case ' ':
319                 /* positive numbers prefixed with ' ' */
320                 status->flags |= E_space;
321                 ++spec;
322                 break;
323             case '0':
324                 /* right-aligned padding done with '0' instead of ' ' */
325                 status->flags |= E_zero;
326                 ++spec;
327                 break;
328             default:
329                 /* not a flag, exit flag parsing */
330                 status->flags |= E_done;
331                 break;
332         }
333     } while ( ! ( status->flags & E_done ) );
334
335     /* Optional field width */
336     if ( *spec == '*' )
337     {
338         /* Retrieve width value from argument stack */
339         int width = va_arg( status->arg, int );
340         if ( width < 0 )
341         {
342             status->flags |= E_minus;
343             status->width = abs( width );
344         }
345         else
346         {
347             status->width = width;
348         }
349         ++spec;
350     }
351     else
352     {
353         /* If a width is given, strtol() will return its value. If not given,
354            strtol() will return zero. In both cases, endptr will point to the
355            rest of the conversion specifier - just what we need.
356         */
357         status->width = (int)strtol( spec, (char**)&spec, 10 );
358     }
359
360     /* Optional precision */
361     if ( *spec == '.' )
362     {
363         ++spec;
364         if ( *spec == '*' )
365         {
366             /* Retrieve precision value from argument stack. A negative value
367                is as if no precision is given - as precision is initalized to
368                EOF (negative), there is no need for testing for negative here.
369             */
370             status->prec = va_arg( status->arg, int );
371             ++spec;
372         }
373         else
374         {
375             status->prec = (int)strtol( spec, (char**) &spec, 10 );
376         }
377         /* Having a precision cancels out any zero flag. */
378         status->flags &= ~E_zero;
379     }
380
381     /* Optional length modifier
382        We step one character ahead in any case, and step back only if we find
383        there has been no length modifier (or step ahead another character if it
384        has been "hh" or "ll").
385     */
386     switch ( *(spec++) )
387     {
388         case 'h':
389             if ( *spec == 'h' )
390             {
391                 /* hh -> char */
392                 status->flags |= E_char;
393                 ++spec;
394             }
395             else
396             {
397                 /* h -> short */
398                 status->flags |= E_short;
399             }
400             break;
401         case 'l':
402             if ( *spec == 'l' )
403             {
404                 /* ll -> long long */
405                 status->flags |= E_llong;
406                 ++spec;
407             }
408             else
409             {
410                 /* k -> long */
411                 status->flags |= E_long;
412             }
413             break;
414         case 'j':
415             /* j -> intmax_t, which might or might not be long long */
416             status->flags |= E_intmax;
417             break;
418         case 'z':
419             /* z -> size_t, which might or might not be unsigned int */
420             status->flags |= E_size;
421             break;
422         case 't':
423             /* t -> ptrdiff_t, which might or might not be long */
424             status->flags |= E_ptrdiff;
425             break;
426         case 'L':
427             /* L -> long double */
428             status->flags |= E_ldouble;
429             break;
430         default:
431             --spec;
432             break;
433     }
434
435     /* Conversion specifier */
436     switch ( *spec )
437     {
438         case 'd':
439             /* FALLTHROUGH */
440         case 'i':
441             status->base = 10;
442             break;
443         case 'o':
444             status->base = 8;
445             status->flags |= E_unsigned;
446             break;
447         case 'u':
448             status->base = 10;
449             status->flags |= E_unsigned;
450             break;
451         case 'x':
452             status->base = 16;
453             status->flags |= ( E_lower | E_unsigned );
454             break;
455         case 'X':
456             status->base = 16;
457             status->flags |= E_unsigned;
458             break;
459         case 'f':
460         case 'F':
461         case 'e':
462         case 'E':
463         case 'g':
464         case 'G':
465             break;
466         case 'a':
467         case 'A':
468             break;
469         case 'c':
470             /* TODO: wide chars. */
471             if ( !printchar( va_arg( status->arg, int ), status ) )
472                 return -1;
473             ++spec;
474             return (spec - orig_spec);
475         case 's':
476             /* TODO: wide chars. */
477             {
478                 char * s = va_arg( status->arg, char * );
479                 if ( !printstr( s, status ) )
480                     return -1;
481                 ++spec;
482                 return (spec - orig_spec);
483             }
484         case 'p':
485             status->base = 16;
486             status->flags |= ( E_lower | E_unsigned | E_alt | E_intptr );
487             break;
488         case 'n':
489            {
490                int * val = va_arg( status->arg, int * );
491                *val = status->i;
492                ++spec;
493                return (spec - orig_spec);
494            }
495         default:
496             /* No conversion specifier. Bad conversion. */
497             return 0;
498     }
499     /* Do the actual output based on our findings */
500     if ( status->base != 0 )
501     {
502         /* Integer conversions */
503         /* TODO: Check for invalid flag combinations. */
504         if ( status->flags & E_unsigned )
505         {
506             uintmax_t value;
507             switch ( status->flags & E_TYPES )
508             {
509                 case E_char:
510                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
511                     break;
512                 case E_short:
513                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
514                     break;
515                 case 0:
516                     value = (uintmax_t)va_arg( status->arg, unsigned int );
517                     break;
518                 case E_long:
519                     value = (uintmax_t)va_arg( status->arg, unsigned long );
520                     break;
521                 case E_llong:
522                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
523                     break;
524                 case E_size:
525                     value = (uintmax_t)va_arg( status->arg, size_t );
526                     break;
527                 case E_intptr:
528                     value = (uintmax_t)va_arg( status->arg, uintptr_t );
529                     break;
530                 case E_ptrdiff:
531                     value = (uintmax_t)va_arg( status->arg, ptrdiff_t );
532                     break;
533                 case E_intmax:
534                     value = va_arg( status->arg, uintmax_t );
535             }
536             if ( !int2base( value, status ) )
537                 return -1;
538         }
539         else
540         {
541             intmax_t value;
542             switch ( status->flags & E_TYPES )
543             {
544                 case E_char:
545                     value = (intmax_t)(char)va_arg( status->arg, int );
546                     break;
547                 case E_short:
548                     value = (intmax_t)(short)va_arg( status->arg, int );
549                     break;
550                 case 0:
551                     value = (intmax_t)va_arg( status->arg, int );
552                     break;
553                 case E_long:
554                     value = (intmax_t)va_arg( status->arg, long );
555                     break;
556                 case E_llong:
557                     value = (intmax_t)va_arg( status->arg, long long );
558                     break;
559                 case E_size:
560                     value = (intmax_t)va_arg( status->arg, size_t );
561                     break;
562                 case E_intptr:
563                     value = (intmax_t)va_arg( status->arg, intptr_t );
564                     break;
565                 case E_ptrdiff:
566                     value = (intmax_t)va_arg( status->arg, ptrdiff_t );
567                     break;
568                 case E_intmax:
569                     value = va_arg( status->arg, intmax_t );
570                     break;
571             }
572
573             if (!int2base( value, status ) )
574                 return -1;
575         }
576
577         if ( status->flags & E_minus && status->current < status->width )
578         {
579             if (!cbrept( status, ' ', status->width - status->current ))
580                 return -1;
581         }
582     }
583     ++spec;
584     return spec - orig_spec;
585 }
586
587 #endif
588
589 #ifdef TEST
590 #define _PDCLIB_FILEID "_PDCLIB/print.c"
591 #define _PDCLIB_STRINGIO
592
593 #include <_PDCLIB_test.h>
594
595 #ifndef REGTEST
596 static size_t testcb( void *p, const char *buf, size_t size )
597 {
598     char **destbuf = p;
599     memcpy(*destbuf, buf, size);
600     *destbuf += size;
601     return size;
602 }
603
604 static int testprintf( char * buffer, const char * format, ... )
605 {
606     /* Members: base, flags, n, i, current, width, prec, ctx, cb, arg      */
607     struct _PDCLIB_status_t status;
608     status.base = 0;
609     status.flags = 0;
610     status.n = 100;
611     status.i = 0;
612     status.current = 0;
613     status.width = 0;
614     status.prec = 0;
615     status.ctx = &buffer;
616     status.write = testcb;
617     va_start( status.arg, format );
618     memset( buffer, '\0', 100 );
619     if ( _PDCLIB_print( format, &status ) != strlen( format ) )
620     {
621         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
622         ++TEST_RESULTS;
623     }
624     va_end( status.arg );
625     return status.i;
626 }
627 #endif
628
629 #define TEST_CONVERSION_ONLY
630
631 int main( void )
632 {
633 #ifndef REGTEST
634     char target[100];
635 #include "printf_testcases.h"
636 #endif
637     return TEST_RESULTS;
638 }
639
640 #endif