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