1 /* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )
3 This file is part of the Public Domain C Library (PDCLib).
4 Permission is granted to use, modify, and / or redistribute at will.
16 /* Using an integer's bits as flags for both the conversion flags and length
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.
22 #define E_minus (1<<0)
25 #define E_space (1<<3)
30 #define E_short (1<<7)
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)
38 #define E_ldouble (1<<14)
40 #define E_lower (1<<15)
41 #define E_unsigned (1<<16)
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
53 if ( status->i < status->n ) { \
54 if ( status->stream != NULL ) \
55 putc( character, status->stream ); \
57 status->s[status->i] = character; \
63 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
65 if ( status->prec < 0 )
69 /* At worst, we need two prefix characters (hex prefix). */
70 char preface[3] = "\0";
72 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) && ( value != 0 ) )
74 /* Octal / hexadecimal prefix for "%#" conversions */
75 preface[ preidx++ ] = '0';
76 if ( status->base == 16 )
78 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
83 /* Negative sign for negative values - at all times. */
84 preface[ preidx++ ] = '-';
86 else if ( ! ( status->flags & E_unsigned ) )
88 /* plus sign / extra space are only for unsigned conversions */
89 if ( status->flags & E_plus )
91 preface[ preidx++ ] = '+';
93 else if ( status->flags & E_space )
95 preface[ preidx++ ] = ' ';
99 /* At this point, status->current has the number of digits queued up.
100 Determine if we have a precision requirement to pad those.
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 ) ) )
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.
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.
114 size_t characters = preidx + ( ( status->current > (_PDCLIB_size_t)status->prec ) ? status->current : (_PDCLIB_size_t)status->prec );
115 if ( status->width > characters )
117 for ( size_t i = 0; i < status->width - characters; ++i )
124 /* Now we did the padding, do the prefixes (if any). */
126 while ( preface[ preidx ] != '\0' )
128 PUT( preface[ preidx++ ] );
131 /* Do the precision padding if necessary. */
132 while ( prec_pads-- > 0 )
137 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
139 /* If field is not left aligned, and zero padding is requested, do
142 while ( status->current < status->width )
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.
163 /* Special case: zero value, zero precision -- no output (but padding) */ \
164 if ( status->current == 0 && value == 0 && status->prec == 0 ) \
166 intformat( value, status ); \
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. \
174 ++(status->current); \
175 if ( ( value / status->base ) != 0 ) \
177 /* More digits to be done - recurse deeper */ \
178 int2base( value / status->base, status ); \
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. \
187 intformat( value, status ); \
189 /* Recursion tail - print the current digit. */ \
191 int digit = value % status->base; \
196 if ( status->flags & E_lower ) \
198 /* Lowercase letters. Same array used for strto...(). */ \
199 PUT( _PDCLIB_digits[ digit ] ); \
203 /* Uppercase letters. Array only used here, only 0-F. */ \
204 PUT( _PDCLIB_Xdigits[ digit ] ); \
211 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
217 static void stringformat( const char * s, struct _PDCLIB_status_t * status )
219 if ( status->flags & E_char )
225 if ( status->prec < 0 )
227 status->prec = strlen( s );
231 for ( int i = 0; i < status->prec; ++i )
241 if ( ! ( status->flags & E_minus ) && ( status->width > (_PDCLIB_size_t)status->prec ) )
243 while ( status->current < ( status->width - status->prec ) )
249 while ( status->prec > 0 )
255 if ( status->flags & E_minus )
257 while ( status->width > status->current )
266 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
268 const char * orig_spec = spec;
269 if ( *(++spec) == '%' )
271 /* %% -> print single '%' */
275 /* Initializing status structure */
282 /* First come 0..n flags */
288 /* left-aligned output */
289 status->flags |= E_minus;
293 /* positive numbers prefixed with '+' */
294 status->flags |= E_plus;
298 /* alternative format (leading 0x for hex, 0 for octal) */
299 status->flags |= E_alt;
303 /* positive numbers prefixed with ' ' */
304 status->flags |= E_space;
308 /* right-aligned padding done with '0' instead of ' ' */
309 status->flags |= E_zero;
313 /* not a flag, exit flag parsing */
314 status->flags |= E_done;
317 } while ( ! ( status->flags & E_done ) );
319 /* Optional field width */
322 /* Retrieve width value from argument stack */
323 int width = va_arg( status->arg, int );
326 status->flags |= E_minus;
327 status->width = abs( width );
331 status->width = width;
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.
341 status->width = (int)strtol( spec, (char**)&spec, 10 );
344 /* Optional precision */
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.
354 status->prec = va_arg( status->arg, int );
360 status->prec = (int)strtol( spec, &endptr, 10 );
361 if ( spec == endptr )
363 /* Decimal point but no number - equals zero */
368 /* Having a precision cancels out any zero flag. */
369 status->flags &= ~E_zero;
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").
383 status->flags |= E_char;
389 status->flags |= E_short;
395 /* ll -> long long */
396 status->flags |= E_llong;
402 status->flags |= E_long;
406 /* j -> intmax_t, which might or might not be long long */
407 status->flags |= E_intmax;
410 /* z -> size_t, which might or might not be unsigned int */
411 status->flags |= E_size;
414 /* t -> ptrdiff_t, which might or might not be long */
415 status->flags |= E_ptrdiff;
418 /* L -> long double */
419 status->flags |= E_ldouble;
426 /* Conversion specifier */
436 status->flags |= E_unsigned;
440 status->flags |= E_unsigned;
444 status->flags |= ( E_lower | E_unsigned );
448 status->flags |= E_unsigned;
461 /* TODO: wide chars. */
464 c[0] = (char)va_arg( status->arg, int );
465 status->flags |= E_char;
466 stringformat( c, status );
470 /* TODO: wide chars. */
471 stringformat( va_arg( status->arg, char * ), status );
475 status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
479 int * val = va_arg( status->arg, int * );
484 /* No conversion specifier. Bad conversion. */
488 /* Do the actual output based on our findings */
489 if ( status->base != 0 )
491 /* Integer conversions */
492 /* TODO: Check for invalid flag combinations. */
493 if ( status->flags & E_unsigned )
496 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
499 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
502 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
505 value = (uintmax_t)va_arg( status->arg, unsigned int );
508 value = (uintmax_t)va_arg( status->arg, unsigned long );
511 value = (uintmax_t)va_arg( status->arg, unsigned long long );
514 value = (uintmax_t)va_arg( status->arg, size_t );
517 value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
520 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
528 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
531 value = (intmax_t)(char)va_arg( status->arg, int );
534 value = (intmax_t)(short)va_arg( status->arg, int );
537 value = (intmax_t)va_arg( status->arg, int );
540 value = (intmax_t)va_arg( status->arg, long );
543 value = (intmax_t)va_arg( status->arg, long long );
546 value = (intmax_t)va_arg( status->arg, ptrdiff_t );
549 value = va_arg( status->arg, intmax_t );
552 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
557 if ( status->flags & E_minus )
559 while ( status->current < status->width )
565 if ( status->i >= status->n && status->n > 0 )
567 status->s[status->n - 1] = '\0';
576 #define _PDCLIB_FILEID "_PDCLIB/print.c"
577 #define _PDCLIB_STRINGIO
579 #include "_PDCLIB_test.h"
583 static int testprintf( char * buffer, const char * format, ... )
585 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
586 struct _PDCLIB_status_t status;
595 status.stream = NULL;
596 va_start( status.arg, format );
597 memset( buffer, '\0', 100 );
598 if ( *(_PDCLIB_print( format, &status )) != '\0' )
600 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
603 va_end( status.arg );
609 #define TEST_CONVERSION_ONLY
615 #include "printf_testcases.h"