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.
15 /* Using an integer's bits as flags for both the conversion flags and length
18 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
19 width flags) into a combined field.
21 #define E_minus (1<<0)
24 #define E_space (1<<3)
29 #define E_short (1<<7)
31 #define E_llong (1<<9)
32 #define E_intmax (1<<10)
33 #define E_size (1<<11)
34 #define E_ptrdiff (1<<12)
35 #define E_pointer (1<<13)
37 #define E_ldouble (1<<14)
39 #define E_lower (1<<15)
40 #define E_unsigned (1<<16)
42 /* This macro delivers a given character to either a memory buffer or a stream,
43 depending on the contents of 'status' (struct _PDCLIB_status_t).
44 x - the character to be delivered
45 i - pointer to number of characters already delivered in this call
46 n - pointer to maximum number of characters to be delivered in this call
47 s - the buffer into which the character shall be delivered
52 if ( status->i < status->n ) { \
53 if ( status->stream != NULL ) \
54 putc( character, status->stream ); \
56 status->s[status->i] = character; \
62 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
64 /* At worst, we need two prefix characters (hex prefix). */
65 char preface[3] = "\0";
67 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) && ( value != 0 ) )
69 /* Octal / hexadecimal prefix for "%#" conversions */
70 preface[ preidx++ ] = '0';
71 if ( status->base == 16 )
73 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
78 /* Negative sign for negative values - at all times. */
79 preface[ preidx++ ] = '-';
81 else if ( ! ( status->flags & E_unsigned ) )
83 /* plus sign / extra space are only for unsigned conversions */
84 if ( status->flags & E_plus )
86 preface[ preidx++ ] = '+';
88 else if ( status->flags & E_space )
90 preface[ preidx++ ] = ' ';
94 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
95 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
97 /* Space padding is only done if no zero padding or left alignment
98 is requested. Leave space for any prefixes determined above.
100 /* The number of characters to be printed, plus prefixes if any. */
101 /* This line contained probably the most stupid, time-wasting bug
102 I've ever perpetrated. Greetings to Samface, DevL, and all
103 sceners at Breakpoint 2006.
105 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
106 if ( status->width > characters )
108 for ( size_t i = 0; i < status->width - characters; ++i )
115 /* Now we did the padding, do the prefixes (if any). */
117 while ( preface[ preidx ] != '\0' )
119 PUT( preface[ preidx++ ] );
122 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
124 /* If field is not left aligned, and zero padding is requested, do
127 while ( status->current < status->width )
133 /* Do the precision padding if necessary. */
134 for ( size_t i = 0; i < prec_pads; ++i )
142 /* This function recursively converts a given integer value to a character
143 stream. The conversion is done under the control of a given status struct
144 and written either to a character string or a stream, depending on that
145 same status struct. The status struct also keeps the function from exceeding
146 snprintf() limits, and enables any necessary padding / prefixing of the
147 output once the number of characters to be printed is known, which happens
148 at the lowermost recursion level.
150 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
152 /* Registering the character being printed at the end of the function here
153 already so it will be taken into account when the deepestmost recursion
154 does the prefix / padding stuff.
157 if ( ( value / status->base ) != 0 )
159 /* More digits to be done - recurse deeper */
160 int2base( value / status->base, status );
164 /* We reached the last digit, the deepest point of our recursion, and
165 only now know how long the number to be printed actually is. Now we
166 have to do the sign, prefix, width, and precision padding stuff
167 before printing the numbers while we resurface from the recursion.
169 intformat( value, status );
171 /* Recursion tail - print the current digit. */
173 int digit = value % status->base;
178 if ( status->flags & E_lower )
180 /* Lowercase letters. Same array used for strto...(). */
181 PUT( _PDCLIB_digits[ digit ] );
185 /* Uppercase letters. Array only used here, only 0-F. */
186 PUT( _PDCLIB_Xdigits[ digit ] );
192 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
194 const char * orig_spec = spec;
195 if ( *(++spec) == '%' )
197 /* %% -> print single '%' */
201 /* Initializing status structure */
208 /* First come 0..n flags */
214 /* left-aligned output */
215 status->flags |= E_minus;
219 /* positive numbers prefixed with '+' */
220 status->flags |= E_plus;
224 /* alternative format (leading 0x for hex, 0 for octal) */
225 status->flags |= E_alt;
229 /* positive numbers prefixed with ' ' */
230 status->flags |= E_space;
234 /* right-aligned padding done with '0' instead of ' ' */
235 status->flags |= E_zero;
239 /* not a flag, exit flag parsing */
240 status->flags |= E_done;
243 } while ( ! ( status->flags & E_done ) );
245 /* Optional field width */
248 /* Retrieve width value from argument stack */
249 int width = va_arg( status->arg, int );
252 status->flags |= E_minus;
253 status->width = abs( width );
257 status->width = width;
263 /* If a width is given, strtol() will return its value. If not given,
264 strtol() will return zero. In both cases, endptr will point to the
265 rest of the conversion specifier - just what we need.
267 status->width = (int)strtol( spec, (char**)&spec, 10 );
270 /* Optional precision */
276 /* Retrieve precision value from argument stack. A negative value
277 is as if no precision is given - as precision is initalized to
278 EOF (negative), there is no need for testing for negative here.
280 status->prec = va_arg( status->arg, int );
285 status->prec = (int)strtol( spec, &endptr, 10 );
286 if ( spec == endptr )
288 /* Decimal point but no number - bad conversion specifier. */
293 /* Having a precision cancels out any zero flag. */
294 status->flags &= ~E_zero;
297 /* Optional length modifier
298 We step one character ahead in any case, and step back only if we find
299 there has been no length modifier (or step ahead another character if it
300 has been "hh" or "ll").
308 status->flags |= E_char;
314 status->flags |= E_short;
320 /* ll -> long long */
321 status->flags |= E_llong;
327 status->flags |= E_long;
331 /* j -> intmax_t, which might or might not be long long */
332 status->flags |= E_intmax;
335 /* z -> size_t, which might or might not be unsigned int */
336 status->flags |= E_size;
339 /* t -> ptrdiff_t, which might or might not be long */
340 status->flags |= E_ptrdiff;
343 /* L -> long double */
344 status->flags |= E_ldouble;
351 /* Conversion specifier */
361 status->flags |= E_unsigned;
365 status->flags |= E_unsigned;
369 status->flags |= ( E_lower | E_unsigned );
373 status->flags |= E_unsigned;
386 /* TODO: Flags, wide chars. */
387 PUT( va_arg( status->arg, int ) );
390 /* TODO: Flags, wide chars. */
392 char * s = va_arg( status->arg, char * );
400 /* TODO: E_long -> E_intptr */
402 status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
406 int * val = va_arg( status->arg, int * );
411 /* No conversion specifier. Bad conversion. */
415 /* Do the actual output based on our findings */
416 if ( status->base != 0 )
418 /* Integer conversions */
419 /* TODO: Check for invalid flag combinations. */
420 if ( status->flags & E_unsigned )
423 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
426 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
429 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
432 value = (uintmax_t)va_arg( status->arg, unsigned int );
435 value = (uintmax_t)va_arg( status->arg, unsigned long );
438 value = (uintmax_t)va_arg( status->arg, unsigned long long );
441 value = (uintmax_t)va_arg( status->arg, size_t );
444 value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
447 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
451 /* FIXME: The if clause means one-digit values do not get formatted */
452 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
453 if ( ( value / status->base ) != 0 )
455 int2base( (intmax_t)(value / status->base), status );
459 intformat( (intmax_t)value, status );
461 int digit = value % status->base;
466 if ( status->flags & E_lower )
468 PUT( _PDCLIB_digits[ digit ] );
472 PUT( _PDCLIB_Xdigits[ digit ] );
477 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
480 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
483 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
486 int2base( (intmax_t)va_arg( status->arg, int ), status );
489 int2base( (intmax_t)va_arg( status->arg, long ), status );
492 int2base( (intmax_t)va_arg( status->arg, long long ), status );
495 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
498 int2base( va_arg( status->arg, intmax_t ), status );
501 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
505 if ( status->flags & E_minus )
507 while ( status->current < status->width )
513 if ( status->i >= status->n && status->n > 0 )
515 status->s[status->n - 1] = '\0';
524 #define _PDCLIB_FILEID "_PDCLIB/print.c"
525 #define _PDCLIB_STRINGIO
527 #include "_PDCLIB_test.h"
531 static int testprintf( char * buffer, const char * format, ... )
533 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
534 struct _PDCLIB_status_t status;
543 status.stream = NULL;
544 va_start( status.arg, format );
545 memset( buffer, '\0', 100 );
546 if ( *(_PDCLIB_print( format, &status )) != '\0' )
548 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
551 va_end( status.arg );
557 #define TEST_CONVERSION_ONLY
563 #include "printf_testcases.h"