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. */
30 #define E_intmax 1<<10
32 #define E_ptrdiff 1<<12
33 #define E_pointer 1<<13
34 #define E_ldouble 1<<14
36 #define E_unsigned 1<<16
38 /* This macro delivers a given character to either a memory buffer or a stream,
39 depending on the contents of 'status' (struct _PDCLIB_status_t).
40 x - the character to be delivered
41 i - pointer to number of characters already delivered in this call
42 n - pointer to maximum number of characters to be delivered in this call
43 s - the buffer into which the character shall be delivered
48 if ( status->i < status->n ) { \
49 if ( status->stream != NULL ) \
50 putc( character, status->stream ); \
52 status->s[status->i] = character; \
58 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
60 /* At worst, we need two prefix characters (hex prefix). */
61 char preface[3] = "\0";
63 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
65 /* Octal / hexadecimal prefix for "%#" conversions */
66 preface[ preidx++ ] = '0';
67 if ( status->base == 16 )
69 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
74 /* Negative sign for negative values - at all times. */
75 preface[ preidx++ ] = '-';
77 else if ( ! ( status->flags & E_unsigned ) )
79 /* plus sign / extra space are only for unsigned conversions */
80 if ( status->flags & E_plus )
82 preface[ preidx++ ] = '+';
84 else if ( status->flags & E_space )
86 preface[ preidx++ ] = ' ';
90 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
91 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
93 /* Space padding is only done if no zero padding or left alignment
94 is requested. Leave space for any prefixes determined above.
96 /* The number of characters to be printed, plus prefixes if any. */
97 /* This line contained probably the most stupid, time-wasting bug
98 I've ever perpetrated. Greetings to Samface, DevL, and all
99 sceners at Breakpoint 2006.
101 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
102 if ( status->width > characters )
104 for ( size_t i = 0; i < status->width - characters; ++i )
111 /* Now we did the padding, do the prefixes (if any). */
113 while ( preface[ preidx ] != '\0' )
115 PUT( preface[ preidx++ ] );
118 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
120 /* If field is not left aligned, and zero padding is requested, do
123 while ( status->current < status->width )
129 /* Do the precision padding if necessary. */
130 for ( size_t i = 0; i < prec_pads; ++i )
138 /* This function recursively converts a given integer value to a character
139 stream. The conversion is done under the control of a given status struct
140 and written either to a character string or a stream, depending on that
141 same status struct. The status struct also keeps the function from exceeding
142 snprintf() limits, and enables any necessary padding / prefixing of the
143 output once the number of characters to be printed is known, which happens
144 at the lowermost recursion level.
146 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
148 /* Registering the character being printed at the end of the function here
149 already so it will be taken into account when the deepestmost recursion
150 does the prefix / padding stuff.
153 if ( ( value / status->base ) != 0 )
155 /* More digits to be done - recurse deeper */
156 int2base( value / status->base, status );
160 /* We reached the last digit, the deepest point of our recursion, and
161 only now know how long the number to be printed actually is. Now we
162 have to do the sign, prefix, width, and precision padding stuff
163 before printing the numbers while we resurface from the recursion.
165 intformat( value, status );
167 /* Recursion tail - print the current digit. */
169 int digit = value % status->base;
174 if ( status->flags & E_lower )
176 /* Lowercase letters. Same array used for strto...(). */
177 PUT( _PDCLIB_digits[ digit ] );
181 /* Uppercase letters. Array only used here, only 0-F. */
182 PUT( _PDCLIB_Xdigits[ digit ] );
188 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
190 const char * orig_spec = spec;
191 if ( *(++spec) == '%' )
193 /* %% -> print single '%' */
197 /* Initializing status structure */
204 /* First come 0..n flags */
210 /* left-aligned output */
211 status->flags |= E_minus;
215 /* positive numbers prefixed with '+' */
216 status->flags |= E_plus;
220 /* alternative format (leading 0x for hex, 0 for octal) */
221 status->flags |= E_alt;
225 /* positive numbers prefixed with ' ' */
226 status->flags |= E_space;
230 /* right-aligned padding done with '0' instead of ' ' */
231 status->flags |= E_zero;
235 /* not a flag, exit flag parsing */
236 status->flags |= E_done;
239 } while ( ! ( status->flags & E_done ) );
241 /* Optional field width */
244 /* Retrieve width value from argument stack */
245 int width = va_arg( status->arg, int );
248 status->flags |= E_minus;
249 status->width = abs( width );
253 status->width = width;
259 /* If a width is given, strtol() will return its value. If not given,
260 strtol() will return zero. In both cases, endptr will point to the
261 rest of the conversion specifier - just what we need.
263 status->width = (int)strtol( spec, (char**)&spec, 10 );
266 /* Optional precision */
272 /* Retrieve precision value from argument stack. A negative value
273 is as if no precision is given - as precision is initalized to
274 EOF (negative), there is no need for testing for negative here.
276 status->prec = va_arg( status->arg, int );
281 status->prec = (int)strtol( spec, &endptr, 10 );
282 if ( spec == endptr )
284 /* Decimal point but no number - bad conversion specifier. */
289 /* Having a precision cancels out any zero flag. */
290 status->flags ^= E_zero;
293 /* Optional length modifier
294 We step one character ahead in any case, and step back only if we find
295 there has been no length modifier (or step ahead another character if it
296 has been "hh" or "ll").
304 status->flags |= E_char;
310 status->flags |= E_short;
316 /* ll -> long long */
317 status->flags |= E_llong;
323 status->flags |= E_long;
327 /* j -> intmax_t, which might or might not be long long */
328 status->flags |= E_intmax;
331 /* z -> size_t, which might or might not be unsigned int */
332 status->flags |= E_size;
335 /* t -> ptrdiff_t, which might or might not be long */
336 status->flags |= E_ptrdiff;
339 /* L -> long double */
340 status->flags |= E_ldouble;
347 /* Conversion specifier */
357 status->flags |= E_unsigned;
361 status->flags |= E_unsigned;
365 status->flags |= ( E_lower | E_unsigned );
369 status->flags |= E_unsigned;
382 /* TODO: Flags, wide chars. */
383 PUT( va_arg( status->arg, int ) );
386 /* TODO: Flags, wide chars. */
388 char * s = va_arg( status->arg, char * );
396 /* TODO: E_long -> E_intptr */
398 status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
402 int * val = va_arg( status->arg, int * );
407 /* No conversion specifier. Bad conversion. */
411 /* Do the actual output based on our findings */
412 if ( status->base != 0 )
414 /* Integer conversions */
415 /* TODO: Check for invalid flag combinations. */
416 if ( status->flags & E_unsigned )
419 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
422 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
425 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
428 value = (uintmax_t)va_arg( status->arg, unsigned int );
431 value = (uintmax_t)va_arg( status->arg, unsigned long );
434 value = (uintmax_t)va_arg( status->arg, unsigned long long );
437 value = (uintmax_t)va_arg( status->arg, size_t );
440 value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
443 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
447 /* FIXME: The if clause means one-digit values do not get formatted */
448 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
449 if ( ( value / status->base ) != 0 )
451 int2base( (intmax_t)(value / status->base), status );
455 intformat( (intmax_t)value, status );
457 int digit = value % status->base;
462 if ( status->flags & E_lower )
464 PUT( _PDCLIB_digits[ digit ] );
468 PUT( _PDCLIB_Xdigits[ digit ] );
473 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
476 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
479 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
482 int2base( (intmax_t)va_arg( status->arg, int ), status );
485 int2base( (intmax_t)va_arg( status->arg, long ), status );
488 int2base( (intmax_t)va_arg( status->arg, long long ), status );
491 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
494 int2base( va_arg( status->arg, intmax_t ), status );
497 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
501 if ( status->flags & E_minus )
503 while ( status->current < status->width )
509 if ( status->i >= status->n && status->n > 0 )
511 status->s[status->n - 1] = '\0';
520 #define _PDCLIB_FILEID "_PDCLIB/print.c"
521 #define _PDCLIB_STRINGIO
523 #include "_PDCLIB_test.h"
527 static int testprintf( char * buffer, const char * format, ... )
529 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
530 struct _PDCLIB_status_t status;
539 status.stream = NULL;
540 va_start( status.arg, format );
541 memset( buffer, '\0', 100 );
542 if ( *(_PDCLIB_print( format, &status )) != '\0' )
544 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
547 va_end( status.arg );
553 #define TEST_CONVERSION_ONLY
559 #include "printf_testcases.h"