3 /* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )
5 This file is part of the Public Domain C Library (PDCLib).
6 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.
31 #define E_intmax 1<<10
33 #define E_ptrdiff 1<<12
34 #define E_intptr 1<<13
35 #define E_ldouble 1<<14
37 #define E_unsigned 1<<16
39 /* This macro delivers a given character to either a memory buffer or a stream,
40 depending on the contents of 'status' (struct _PDCLIB_status_t).
41 x - the character to be delivered
42 i - pointer to number of characters already delivered in this call
43 n - pointer to maximum number of characters to be delivered in this call
44 s - the buffer into which the character shall be delivered
49 if ( status->i < status->n ) { \
50 if ( status->stream != NULL ) \
51 putc( character, status->stream ); \
53 status->s[status->i] = character; \
59 typedef int32_t intfmt_t;
60 typedef uint32_t uintfmt_t;
62 static void intformat( intfmt_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 ) )
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.
151 #define INT_MAX_WIDTH (sizeof(intfmt_t) * 8 + 2) / 3
153 static void int2base( intfmt_t value, struct _PDCLIB_status_t * status )
155 char digits[INT_MAX_WIDTH + 1];
156 char c, *d = &digits[sizeof(digits)];
157 intfmt_t last_value = value;
162 int digit = value % status->base;
165 value /= status->base;
171 Account for character in the output
175 if ( (status->flags & E_lower) )
176 *--d = _PDCLIB_digits[digit];
178 *--d = _PDCLIB_Xdigits[digit];
179 } while (value != 0);
181 /* We reached the last digit, and only now know how long the
182 number to be printed actually is. Now we have to do the sign,
183 prefix, width, and precision padding stuff before printing the
187 intformat( last_value, status );
194 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
196 const char * orig_spec = spec;
197 if ( *(++spec) == '%' )
199 /* %% -> print single '%' */
203 /* Initializing status structure */
210 /* First come 0..n flags */
216 /* left-aligned output */
217 status->flags |= E_minus;
221 /* positive numbers prefixed with '+' */
222 status->flags |= E_plus;
226 /* alternative format (leading 0x for hex, 0 for octal) */
227 status->flags |= E_alt;
231 /* positive numbers prefixed with ' ' */
232 status->flags |= E_space;
236 /* right-aligned padding done with '0' instead of ' ' */
237 status->flags |= E_zero;
241 /* not a flag, exit flag parsing */
242 status->flags |= E_done;
245 } while ( ! ( status->flags & E_done ) );
247 /* Optional field width */
250 /* Retrieve width value from argument stack */
251 int width = va_arg( status->arg, int );
254 status->flags |= E_minus;
255 status->width = abs( width );
259 status->width = width;
265 /* If a width is given, strtol() will return its value. If not given,
266 strtol() will return zero. In both cases, endptr will point to the
267 rest of the conversion specifier - just what we need.
269 status->width = (int)strtol( spec, (char**)&spec, 10 );
272 /* Optional precision */
278 /* Retrieve precision value from argument stack. A negative value
279 is as if no precision is given - as precision is initalized to
280 EOF (negative), there is no need for testing for negative here.
282 status->prec = va_arg( status->arg, int );
287 status->prec = (int)strtol( spec, &endptr, 10 );
288 if ( spec == endptr )
290 /* Decimal point but no number - bad conversion specifier. */
295 /* Having a precision cancels out any zero flag. */
296 status->flags ^= E_zero;
299 /* Optional length modifier
300 We step one character ahead in any case, and step back only if we find
301 there has been no length modifier (or step ahead another character if it
302 has been "hh" or "ll").
310 status->flags |= E_char;
316 status->flags |= E_short;
322 /* ll -> long long */
323 status->flags |= E_llong;
329 status->flags |= E_long;
333 /* j -> intfmt_t, which might or might not be long long */
334 status->flags |= E_intmax;
337 /* z -> size_t, which might or might not be unsigned int */
338 status->flags |= E_size;
341 /* t -> ptrdiff_t, which might or might not be long */
342 status->flags |= E_ptrdiff;
345 /* L -> long double */
346 status->flags |= E_ldouble;
353 /* Conversion specifier */
363 status->flags |= E_unsigned;
367 status->flags |= E_unsigned;
371 status->flags |= ( E_lower | E_unsigned );
375 status->flags |= E_unsigned;
388 /* TODO: Flags, wide chars. */
389 PUT( va_arg( status->arg, int ) );
392 /* TODO: Flags, wide chars. */
394 char * s = va_arg( status->arg, char * );
402 /* TODO: E_long -> E_intptr */
404 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
408 int * val = va_arg( status->arg, int * );
413 /* No conversion specifier. Bad conversion. */
417 /* Do the actual output based on our findings */
418 if ( status->base != 0 )
420 /* Integer conversions */
421 /* TODO: Check for invalid flag combinations. */
422 if ( status->flags & E_unsigned )
425 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
428 value = (uintfmt_t)(unsigned char)va_arg( status->arg, int );
431 value = (uintfmt_t)(unsigned short)va_arg( status->arg, int );
434 value = (uintfmt_t)va_arg( status->arg, unsigned int );
437 value = (uintfmt_t)va_arg( status->arg, unsigned long );
440 value = (uintfmt_t)va_arg( status->arg, unsigned long long );
443 value = (uintfmt_t)va_arg( status->arg, size_t );
447 /* FIXME: The if clause means one-digit values do not get formatted */
448 /* Was introduced originally to get value to "safe" levels re. uintfmt_t. */
449 if ( ( value / status->base ) != 0 )
451 int2base( (intfmt_t)(value / status->base), status );
455 intformat( (intfmt_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( (intfmt_t)(char)va_arg( status->arg, int ), status );
479 int2base( (intfmt_t)(short)va_arg( status->arg, int ), status );
482 int2base( (intfmt_t)va_arg( status->arg, int ), status );
485 int2base( (intfmt_t)va_arg( status->arg, long ), status );
488 int2base( (intfmt_t)va_arg( status->arg, long long ), status );
491 int2base( (intfmt_t)va_arg( status->arg, ptrdiff_t ), status );
494 int2base( va_arg( status->arg, intfmt_t ), status );
498 if ( status->flags & E_minus )
500 while ( status->current < status->width )
506 if ( status->i >= status->n && status->n > 0 )
508 status->s[status->n - 1] = '\0';
515 #define _PDCLIB_FILEID "_PDCLIB/print.c"
516 #define _PDCLIB_STRINGIO
518 #include <_PDCLIB_test.h>
520 static int testprintf( char * buffer, const char * format, ... )
522 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
523 struct _PDCLIB_status_t status;
532 status.stream = NULL;
533 va_start( status.arg, format );
534 memset( buffer, '\0', 100 );
535 if ( *(_PDCLIB_print( format, &status )) != '\0' )
537 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
540 va_end( status.arg );
544 #define TEST_CONVERSION_ONLY
549 #include "printf_testcases.h"