]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/print.c
remove() implemented directly without glue trampoline.
[pdclib] / functions / _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 <stdlib.h>
11 #include <stddef.h>
12 #include <string.h>
13
14 #ifndef REGTEST
15
16 /* Using an integer's bits as flags for both the conversion flags and length
17    modifiers.
18 */
19 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
20           width flags) into a combined field.
21 */
22 #define E_minus    (1<<0)
23 #define E_plus     (1<<1)
24 #define E_alt      (1<<2)
25 #define E_space    (1<<3)
26 #define E_zero     (1<<4)
27 #define E_done     (1<<5)
28
29 #define E_char     (1<<6)
30 #define E_short    (1<<7)
31 #define E_long     (1<<8)
32 #define E_llong    (1<<9)
33 #define E_intmax   (1<<10)
34 #define E_size     (1<<11)
35 #define E_ptrdiff  (1<<12)
36 #define E_pointer  (1<<13)
37
38 #define E_ldouble  (1<<14)
39
40 #define E_lower    (1<<15)
41 #define E_unsigned (1<<16)
42
43 /* This macro delivers a given character to either a memory buffer or a stream,
44    depending on the contents of 'status' (struct _PDCLIB_status_t).
45    x - the character to be delivered
46    i - pointer to number of characters already delivered in this call
47    n - pointer to maximum number of characters to be delivered in this call
48    s - the buffer into which the character shall be delivered
49 */
50 #define PUT( x ) \
51 do { \
52     int character = x; \
53     if ( status->i < status->n ) { \
54         if ( status->stream != NULL ) \
55             putc( character, status->stream ); \
56         else \
57             status->s[status->i] = character; \
58     } \
59     ++(status->i); \
60 } while ( 0 )
61
62
63 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
64 {
65     if ( status->prec < 0 )
66     {
67         status->prec = 1;
68     }
69     /* At worst, we need two prefix characters (hex prefix). */
70     char preface[3] = "\0";
71     size_t preidx = 0;
72     if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) && ( value != 0 ) )
73     {
74         /* Octal / hexadecimal prefix for "%#" conversions */
75         preface[ preidx++ ] = '0';
76         if ( status->base == 16 )
77         {
78             preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
79         }
80     }
81     if ( value < 0 )
82     {
83         /* Negative sign for negative values - at all times. */
84         preface[ preidx++ ] = '-';
85     }
86     else if ( ! ( status->flags & E_unsigned ) )
87     {
88         /* plus sign / extra space are only for unsigned conversions */
89         if ( status->flags & E_plus )
90         {
91             preface[ preidx++ ] = '+';
92         }
93         else if ( status->flags & E_space )
94         {
95             preface[ preidx++ ] = ' ';
96         }
97     }
98     {
99     /* At this point, status->current has the number of digits queued up.
100        Determine if we have a precision requirement to pad those.
101     */
102     size_t prec_pads = ( (_PDCLIB_size_t)status->prec > status->current ) ? ( (_PDCLIB_size_t)status->prec - status->current ) : 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. Calculate the number of characters that WILL be
107            printed, including any prefixes determined above.
108         */
109         /* The number of characters to be printed, plus prefixes if any. */
110         /* This line contained probably the most stupid, time-wasting bug
111            I've ever perpetrated. Greetings to Samface, DevL, and all
112            sceners at Breakpoint 2006.
113         */
114         size_t characters = preidx + ( ( status->current > (_PDCLIB_size_t)status->prec ) ? status->current : (_PDCLIB_size_t)status->prec );
115         if ( status->width > characters )
116         {
117             for ( size_t i = 0; i < status->width - characters; ++i )
118             {
119                 PUT( ' ' );
120                 ++(status->current);
121             }
122         }
123     }
124     /* Now we did the padding, do the prefixes (if any). */
125     preidx = 0;
126     while ( preface[ preidx ] != '\0' )
127     {
128         PUT( preface[ preidx++ ] );
129         ++(status->current);
130     }
131     /* Do the precision padding if necessary. */
132     while ( prec_pads-- > 0 )
133     {
134         PUT( '0' );
135         ++(status->current);
136     }
137     if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
138     {
139         /* If field is not left aligned, and zero padding is requested, do
140            so.
141         */
142         while ( status->current < status->width )
143         {
144             PUT( '0' );
145             ++(status->current);
146         }
147     }
148     }
149 }
150
151
152 /* This function recursively converts a given integer value to a character
153    stream. The conversion is done under the control of a given status struct
154    and written either to a character string or a stream, depending on that
155    same status struct. The status struct also keeps the function from exceeding
156    snprintf() limits, and enables any necessary padding / prefixing of the
157    output once the number of characters to be printed is known, which happens
158    at the lowermost recursion level.
159 */
160 #define INT2BASE() \
161 do \
162 { \
163     /* Special case: zero value, zero precision -- no output (but padding) */ \
164     if ( status->current == 0 && value == 0 && status->prec == 0 ) \
165     { \
166         intformat( value, status ); \
167     } \
168     else \
169     { \
170         /* Registering the character being printed at the end of the function here \
171            already so it will be taken into account when the deepestmost recursion \
172            does the prefix / padding stuff. \
173         */ \
174         ++(status->current); \
175         if ( ( value / status->base ) != 0 ) \
176         { \
177             /* More digits to be done - recurse deeper */ \
178             int2base( value / status->base, status ); \
179         } \
180         else \
181         { \
182             /* We reached the last digit, the deepest point of our recursion, and \
183                only now know how long the number to be printed actually is. Now we \
184                have to do the sign, prefix, width, and precision padding stuff \
185                before printing the numbers while we resurface from the recursion. \
186             */ \
187             intformat( value, status ); \
188         } \
189         /* Recursion tail - print the current digit. */ \
190         { \
191         int digit = value % status->base; \
192         if ( digit < 0 ) \
193         { \
194             digit *= -1; \
195         } \
196         if ( status->flags & E_lower ) \
197         { \
198             /* Lowercase letters. Same array used for strto...(). */ \
199             PUT( _PDCLIB_digits[ digit ] ); \
200         } \
201         else \
202         { \
203             /* Uppercase letters. Array only used here, only 0-F. */ \
204             PUT( _PDCLIB_Xdigits[ digit ] ); \
205         } \
206         } \
207     } \
208 } while ( 0 )
209
210
211 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
212 {
213     INT2BASE();
214 }
215
216
217 static void stringformat( const char * s, struct _PDCLIB_status_t * status )
218 {
219     if ( status->flags & E_char )
220     {
221         status->prec = 1;
222     }
223     else
224     {
225         if ( status->prec < 0 )
226         {
227             status->prec = strlen( s );
228         }
229         else
230         {
231             for ( int i = 0; i < status->prec; ++i )
232             {
233                 if ( s[i] == 0 )
234                 {
235                     status->prec = i;
236                     break;
237                 }
238             }
239         }
240     }
241     if ( ! ( status->flags & E_minus ) && ( status->width > (_PDCLIB_size_t)status->prec ) )
242     {
243         while ( status->current < ( status->width - status->prec ) )
244         {
245             PUT( ' ' );
246             ++(status->current);
247         }
248     }
249     while ( status->prec > 0 )
250     {
251         PUT( *(s++) );
252         --(status->prec);
253         ++(status->current);
254     }
255     if ( status->flags & E_minus )
256     {
257         while ( status->width > status->current )
258         {
259             PUT( ' ' );
260             ++(status->current);
261         }
262     }
263 }
264
265
266 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
267 {
268     const char * orig_spec = spec;
269     if ( *(++spec) == '%' )
270     {
271         /* %% -> print single '%' */
272         PUT( *spec );
273         return ++spec;
274     }
275     /* Initializing status structure */
276     status->flags = 0;
277     status->base  = 0;
278     status->current  = 0;
279     status->width = 0;
280     status->prec  = EOF;
281
282     /* First come 0..n flags */
283     do
284     {
285         switch ( *spec )
286         {
287             case '-':
288                 /* left-aligned output */
289                 status->flags |= E_minus;
290                 ++spec;
291                 break;
292             case '+':
293                 /* positive numbers prefixed with '+' */
294                 status->flags |= E_plus;
295                 ++spec;
296                 break;
297             case '#':
298                 /* alternative format (leading 0x for hex, 0 for octal) */
299                 status->flags |= E_alt;
300                 ++spec;
301                 break;
302             case ' ':
303                 /* positive numbers prefixed with ' ' */
304                 status->flags |= E_space;
305                 ++spec;
306                 break;
307             case '0':
308                 /* right-aligned padding done with '0' instead of ' ' */
309                 status->flags |= E_zero;
310                 ++spec;
311                 break;
312             default:
313                 /* not a flag, exit flag parsing */
314                 status->flags |= E_done;
315                 break;
316         }
317     } while ( ! ( status->flags & E_done ) );
318
319     /* Optional field width */
320     if ( *spec == '*' )
321     {
322         /* Retrieve width value from argument stack */
323         int width = va_arg( status->arg, int );
324         if ( width < 0 )
325         {
326             status->flags |= E_minus;
327             status->width = abs( width );
328         }
329         else
330         {
331             status->width = width;
332         }
333         ++spec;
334     }
335     else
336     {
337         /* If a width is given, strtol() will return its value. If not given,
338            strtol() will return zero. In both cases, endptr will point to the
339            rest of the conversion specifier - just what we need.
340         */
341         status->width = (int)strtol( spec, (char**)&spec, 10 );
342     }
343
344     /* Optional precision */
345     if ( *spec == '.' )
346     {
347         ++spec;
348         if ( *spec == '*' )
349         {
350             /* Retrieve precision value from argument stack. A negative value
351                is as if no precision is given - as precision is initalized to
352                EOF (negative), there is no need for testing for negative here.
353             */
354             status->prec = va_arg( status->arg, int );
355             ++spec;
356         }
357         else
358         {
359             char * endptr;
360             status->prec = (int)strtol( spec, &endptr, 10 );
361             if ( spec == endptr )
362             {
363                 /* Decimal point but no number - equals zero */
364                 status->prec = 0;
365             }
366             spec = endptr;
367         }
368         /* Having a precision cancels out any zero flag. */
369         status->flags &= ~E_zero;
370     }
371
372     /* Optional length modifier
373        We step one character ahead in any case, and step back only if we find
374        there has been no length modifier (or step ahead another character if it
375        has been "hh" or "ll").
376     */
377     switch ( *(spec++) )
378     {
379         case 'h':
380             if ( *spec == 'h' )
381             {
382                 /* hh -> char */
383                 status->flags |= E_char;
384                 ++spec;
385             }
386             else
387             {
388                 /* h -> short */
389                 status->flags |= E_short;
390             }
391             break;
392         case 'l':
393             if ( *spec == 'l' )
394             {
395                 /* ll -> long long */
396                 status->flags |= E_llong;
397                 ++spec;
398             }
399             else
400             {
401                 /* k -> long */
402                 status->flags |= E_long;
403             }
404             break;
405         case 'j':
406             /* j -> intmax_t, which might or might not be long long */
407             status->flags |= E_intmax;
408             break;
409         case 'z':
410             /* z -> size_t, which might or might not be unsigned int */
411             status->flags |= E_size;
412             break;
413         case 't':
414             /* t -> ptrdiff_t, which might or might not be long */
415             status->flags |= E_ptrdiff;
416             break;
417         case 'L':
418             /* L -> long double */
419             status->flags |= E_ldouble;
420             break;
421         default:
422             --spec;
423             break;
424     }
425
426     /* Conversion specifier */
427     switch ( *spec )
428     {
429         case 'd':
430             /* FALLTHROUGH */
431         case 'i':
432             status->base = 10;
433             break;
434         case 'o':
435             status->base = 8;
436             status->flags |= E_unsigned;
437             break;
438         case 'u':
439             status->base = 10;
440             status->flags |= E_unsigned;
441             break;
442         case 'x':
443             status->base = 16;
444             status->flags |= ( E_lower | E_unsigned );
445             break;
446         case 'X':
447             status->base = 16;
448             status->flags |= E_unsigned;
449             break;
450         case 'f':
451         case 'F':
452         case 'e':
453         case 'E':
454         case 'g':
455         case 'G':
456             break;
457         case 'a':
458         case 'A':
459             break;
460         case 'c':
461             /* TODO: wide chars. */
462             {
463                 char c[1];
464                 c[0] = (char)va_arg( status->arg, int );
465                 status->flags |= E_char;
466                 stringformat( c, status );
467                 return ++spec;
468             }
469         case 's':
470             /* TODO: wide chars. */
471             stringformat( va_arg( status->arg, char * ), status );
472             return ++spec;
473         case 'p':
474             status->base = 16;
475             status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
476             break;
477         case 'n':
478            {
479                int * val = va_arg( status->arg, int * );
480                *val = status->i;
481                return ++spec;
482            }
483         default:
484             /* No conversion specifier. Bad conversion. */
485             return orig_spec;
486     }
487
488     /* Do the actual output based on our findings */
489     if ( status->base != 0 )
490     {
491         /* Integer conversions */
492         /* TODO: Check for invalid flag combinations. */
493         if ( status->flags & E_unsigned )
494         {
495             uintmax_t value;
496             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
497             {
498                 case E_char:
499                     value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
500                     break;
501                 case E_short:
502                     value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
503                     break;
504                 case 0:
505                     value = (uintmax_t)va_arg( status->arg, unsigned int );
506                     break;
507                 case E_long:
508                     value = (uintmax_t)va_arg( status->arg, unsigned long );
509                     break;
510                 case E_llong:
511                     value = (uintmax_t)va_arg( status->arg, unsigned long long );
512                     break;
513                 case E_size:
514                     value = (uintmax_t)va_arg( status->arg, size_t );
515                     break;
516                 case E_pointer:
517                     value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
518                     break;
519                 default:
520                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
521                     return NULL;
522             }
523             INT2BASE();
524         }
525         else
526         {
527             intmax_t value;
528             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
529             {
530                 case E_char:
531                     value = (intmax_t)(char)va_arg( status->arg, int );
532                     break;
533                 case E_short:
534                     value = (intmax_t)(short)va_arg( status->arg, int );
535                     break;
536                 case 0:
537                     value = (intmax_t)va_arg( status->arg, int );
538                     break;
539                 case E_long:
540                     value = (intmax_t)va_arg( status->arg, long );
541                     break;
542                 case E_llong:
543                     value = (intmax_t)va_arg( status->arg, long long );
544                     break;
545                 case E_ptrdiff:
546                     value = (intmax_t)va_arg( status->arg, ptrdiff_t );
547                     break;
548                 case E_intmax:
549                     value = va_arg( status->arg, intmax_t );
550                     break;
551                 default:
552                     puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
553                     return NULL;
554             }
555             INT2BASE();
556         }
557         if ( status->flags & E_minus )
558         {
559             while ( status->current < status->width )
560             {
561                 PUT( ' ' );
562                 ++(status->current);
563             }
564         }
565         if ( status->i >= status->n && status->n > 0 )
566         {
567             status->s[status->n - 1] = '\0';
568         }
569     }
570     return ++spec;
571 }
572
573 #endif
574
575 #ifdef TEST
576 #define _PDCLIB_FILEID "_PDCLIB/print.c"
577 #define _PDCLIB_STRINGIO
578
579 #include "_PDCLIB_test.h"
580
581 #ifndef REGTEST
582
583 static int testprintf( char * buffer, const char * format, ... )
584 {
585     /* Members: base, flags, n, i, current, s, width, prec, stream, arg      */
586     struct _PDCLIB_status_t status;
587     status.base = 0;
588     status.flags = 0;
589     status.n = 100;
590     status.i = 0;
591     status.current = 0;
592     status.s = buffer;
593     status.width = 0;
594     status.prec = EOF;
595     status.stream = NULL;
596     va_start( status.arg, format );
597     memset( buffer, '\0', 100 );
598     if ( *(_PDCLIB_print( format, &status )) != '\0' )
599     {
600         printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
601         ++TEST_RESULTS;
602     }
603     va_end( status.arg );
604     return status.i;
605 }
606
607 #endif
608
609 #define TEST_CONVERSION_ONLY
610
611 int main( void )
612 {
613 #ifndef REGTEST
614     char target[100];
615 #include "printf_testcases.h"
616 #endif
617     return TEST_RESULTS;
618 }
619
620 #endif