]> pd.if.org Git - pdclib/blob - functions/stdio/_PDCLIB_print.c
PDCLib includes with quotes, not <>.
[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             /* TODO: Marking the default case _PDCLIB_UNREACHABLE breaks %ju test driver? */
505             uintmax_t value = 0;
506             switch ( status->flags & E_TYPES )
507             {
508                 case E_char:
509                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
510                     break;
511                 case E_short:
512                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
513                     break;
514                 case 0:
515                     value = (uintmax_t)va_arg( status->arg, unsigned int );
516                     break;
517                 case E_long:
518                     value = (uintmax_t)va_arg( status->arg, unsigned long );
519                     break;
520                 case E_llong:
521                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
522                     break;
523                 case E_size:
524                     value = (uintmax_t)va_arg( status->arg, size_t );
525                     break;
526                 case E_intptr:
527                     value = (uintmax_t)va_arg( status->arg, uintptr_t );
528                     break;
529                 case E_ptrdiff:
530                     value = (uintmax_t)va_arg( status->arg, ptrdiff_t );
531                     break;
532                 case E_intmax:
533                     value = va_arg( status->arg, uintmax_t );
534             }
535             if ( !int2base( value, status ) )
536                 return -1;
537         }
538         else
539         {
540             intmax_t value = 0;
541             switch ( status->flags & E_TYPES )
542             {
543                 case E_char:
544                     value = (intmax_t)(char)va_arg( status->arg, int );
545                     break;
546                 case E_short:
547                     value = (intmax_t)(short)va_arg( status->arg, int );
548                     break;
549                 case 0:
550                     value = (intmax_t)va_arg( status->arg, int );
551                     break;
552                 case E_long:
553                     value = (intmax_t)va_arg( status->arg, long );
554                     break;
555                 case E_llong:
556                     value = (intmax_t)va_arg( status->arg, long long );
557                     break;
558                 case E_size:
559                     value = (intmax_t)va_arg( status->arg, size_t );
560                     break;
561                 case E_intptr:
562                     value = (intmax_t)va_arg( status->arg, intptr_t );
563                     break;
564                 case E_ptrdiff:
565                     value = (intmax_t)va_arg( status->arg, ptrdiff_t );
566                     break;
567                 case E_intmax:
568                     value = va_arg( status->arg, intmax_t );
569                     break;
570                 default:
571                     _PDCLIB_UNREACHABLE;
572             }
573
574             if (!int2base( value, status ) )
575                 return -1;
576         }
577
578         if ( status->flags & E_minus && status->current < status->width )
579         {
580             if (!cbrept( status, ' ', status->width - status->current ))
581                 return -1;
582         }
583     }
584     ++spec;
585     return spec - orig_spec;
586 }
587
588 #endif
589
590 #ifdef TEST
591 #define _PDCLIB_FILEID "_PDCLIB/print.c"
592 #define _PDCLIB_STRINGIO
593
594 #include "_PDCLIB_test.h"
595
596 #ifndef REGTEST
597 static size_t testcb( void *p, const char *buf, size_t size )
598 {
599     char **destbuf = p;
600     memcpy(*destbuf, buf, size);
601     *destbuf += size;
602     return size;
603 }
604
605 static int testprintf( char * buffer, const char * format, ... )
606 {
607     /* Members: base, flags, n, i, current, width, prec, ctx, cb, arg      */
608     struct _PDCLIB_status_t status;
609     status.base = 0;
610     status.flags = 0;
611     status.n = 100;
612     status.i = 0;
613     status.current = 0;
614     status.width = 0;
615     status.prec = 0;
616     status.ctx = &buffer;
617     status.write = testcb;
618     va_start( status.arg, format );
619     memset( buffer, '\0', 100 );
620     if ( _PDCLIB_print( format, &status ) != (int)strlen( format ) )
621     {
622         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
623         ++TEST_RESULTS;
624     }
625     va_end( status.arg );
626     return status.i;
627 }
628 #endif
629
630 #define TEST_CONVERSION_ONLY
631
632 int main( void )
633 {
634 #ifndef REGTEST
635     char target[100];
636 #include "printf_testcases.h"
637 #endif
638     return TEST_RESULTS;
639 }
640
641 #endif