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
45 TODO: ref. fputs() for a better way to buffer handling
47 #define DELIVER( x ) \
50 if ( status->i < status->n ) { \
51 if ( status->stream != NULL ) \
52 putc( character, status->stream ); \
54 status->s[status->i] = character; \
60 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
62 /* At worst, we need two prefix characters (hex prefix). */
63 char preface[3] = "\0";
65 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
67 /* Octal / hexadecimal prefix for "%#" conversions */
68 preface[ preidx++ ] = '0';
69 if ( status->base == 16 )
71 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
76 /* Negative sign for negative values - at all times. */
77 preface[ preidx++ ] = '-';
79 else if ( ! ( status->flags & E_unsigned ) )
81 /* plus sign / extra space are only for unsigned conversions */
82 if ( status->flags & E_plus )
84 preface[ preidx++ ] = '+';
86 else if ( status->flags & E_space )
88 preface[ preidx++ ] = ' ';
92 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
93 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
95 /* Space padding is only done if no zero padding or left alignment
96 is requested. Leave space for any prefixes determined above.
98 /* The number of characters to be printed, plus prefixes if any. */
99 /* This line contained probably the most stupid, time-wasting bug
100 I've ever perpetrated. Greetings to Samface, DevL, and all
101 sceners at Breakpoint 2006.
103 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
104 if ( status->width > characters )
106 for ( size_t i = 0; i < status->width - characters; ++i )
113 /* Now we did the padding, do the prefixes (if any). */
115 while ( preface[ preidx ] != '\0' )
117 DELIVER( preface[ preidx++ ] );
120 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
122 /* If field is not left aligned, and zero padding is requested, do
125 while ( status->current < status->width )
131 /* Do the precision padding if necessary. */
132 for ( size_t i = 0; i < prec_pads; ++i )
140 /* This function recursively converts a given integer value to a character
141 stream. The conversion is done under the control of a given status struct
142 and written either to a character string or a stream, depending on that
143 same status struct. The status struct also keeps the function from exceeding
144 snprintf() limits, and enables any necessary padding / prefixing of the
145 output once the number of characters to be printed is known, which happens
146 at the lowermost recursion level.
148 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
150 /* Registering the character being printed at the end of the function here
151 already so it will be taken into account when the deepestmost recursion
152 does the prefix / padding stuff.
155 if ( ( value / status->base ) != 0 )
157 /* More digits to be done - recurse deeper */
158 int2base( value / status->base, status );
162 /* We reached the last digit, the deepest point of our recursion, and
163 only now know how long the number to be printed actually is. Now we
164 have to do the sign, prefix, width, and precision padding stuff
165 before printing the numbers while we resurface from the recursion.
167 intformat( value, status );
169 /* Recursion tail - print the current digit. */
171 int digit = value % status->base;
176 if ( status->flags & E_lower )
178 /* Lowercase letters. Same array used for strto...(). */
179 DELIVER( _PDCLIB_digits[ digit ] );
183 /* Uppercase letters. Array only used here, only 0-F. */
184 DELIVER( _PDCLIB_Xdigits[ digit ] );
190 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
192 const char * orig_spec = spec;
193 if ( *(++spec) == '%' )
195 /* %% -> print single '%' */
199 /* Initializing status structure */
206 /* First come 0..n flags */
212 /* left-aligned output */
213 status->flags |= E_minus;
217 /* positive numbers prefixed with '+' */
218 status->flags |= E_plus;
222 /* alternative format (leading 0x for hex, 0 for octal) */
223 status->flags |= E_alt;
227 /* positive numbers prefixed with ' ' */
228 status->flags |= E_space;
232 /* right-aligned padding done with '0' instead of ' ' */
233 status->flags |= E_zero;
237 /* not a flag, exit flag parsing */
238 status->flags |= E_done;
241 } while ( ! ( status->flags & E_done ) );
243 /* Optional field width */
246 /* Retrieve width value from argument stack */
247 int width = va_arg( status->arg, int );
250 status->flags |= E_minus;
251 status->width = abs( width );
255 status->width = width;
261 /* If a width is given, strtol() will return its value. If not given,
262 strtol() will return zero. In both cases, endptr will point to the
263 rest of the conversion specifier - just what we need.
265 status->width = (int)strtol( spec, (char**)&spec, 10 );
268 /* Optional precision */
274 /* Retrieve precision value from argument stack. A negative value
275 is as if no precision is given - as precision is initalized to
276 EOF (negative), there is no need for testing for negative here.
278 status->prec = va_arg( status->arg, int );
283 status->prec = (int)strtol( spec, &endptr, 10 );
284 if ( spec == endptr )
286 /* Decimal point but no number - bad conversion specifier. */
291 /* Having a precision cancels out any zero flag. */
292 status->flags ^= E_zero;
295 /* Optional length modifier
296 We step one character ahead in any case, and step back only if we find
297 there has been no length modifier (or step ahead another character if it
298 has been "hh" or "ll").
306 status->flags |= E_char;
312 status->flags |= E_short;
318 /* ll -> long long */
319 status->flags |= E_llong;
325 status->flags |= E_long;
329 /* j -> intmax_t, which might or might not be long long */
330 status->flags |= E_intmax;
333 /* z -> size_t, which might or might not be unsigned int */
334 status->flags |= E_size;
337 /* t -> ptrdiff_t, which might or might not be long */
338 status->flags |= E_ptrdiff;
341 /* L -> long double */
342 status->flags |= E_ldouble;
349 /* Conversion specifier */
359 status->flags |= E_unsigned;
363 status->flags |= E_unsigned;
367 status->flags |= ( E_lower | E_unsigned );
371 status->flags |= E_unsigned;
384 /* TODO: Flags, wide chars. */
385 DELIVER( va_arg( status->arg, int ) );
388 /* TODO: Flags, wide chars. */
390 char * s = va_arg( status->arg, char * );
398 /* TODO: E_long -> E_intptr */
400 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
404 int * val = va_arg( status->arg, int * );
409 /* No conversion specifier. Bad conversion. */
413 /* Do the actual output based on our findings */
414 if ( status->base != 0 )
416 /* Integer conversions */
417 /* TODO: Check for invalid flag combinations. */
418 if ( status->flags & E_unsigned )
421 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
424 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
427 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
430 value = (uintmax_t)va_arg( status->arg, unsigned int );
433 value = (uintmax_t)va_arg( status->arg, unsigned long );
436 value = (uintmax_t)va_arg( status->arg, unsigned long long );
439 value = (uintmax_t)va_arg( status->arg, size_t );
443 /* FIXME: The if clause means one-digit values do not get formatted */
444 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
445 if ( ( value / status->base ) != 0 )
447 int2base( (intmax_t)(value / status->base), status );
451 intformat( (intmax_t)value, status );
453 int digit = value % status->base;
458 if ( status->flags & E_lower )
460 DELIVER( _PDCLIB_digits[ digit ] );
464 DELIVER( _PDCLIB_Xdigits[ digit ] );
469 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
472 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
475 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
478 int2base( (intmax_t)va_arg( status->arg, int ), status );
481 int2base( (intmax_t)va_arg( status->arg, long ), status );
484 int2base( (intmax_t)va_arg( status->arg, long long ), status );
487 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
490 int2base( va_arg( status->arg, intmax_t ), status );
494 if ( status->flags & E_minus )
496 while ( status->current < status->width )
502 if ( status->i >= status->n && status->n > 0 )
504 status->s[status->n - 1] = '\0';
511 #include <_PDCLIB_test.h>
516 static int testprintf( char * buffer, const char * format, ... )
518 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
519 struct _PDCLIB_status_t status;
528 status.stream = NULL;
529 va_start( status.arg, format );
530 memset( buffer, '\0', 100 );
531 if ( *(_PDCLIB_print( format, &status )) != '\0' )
533 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
536 va_end( status.arg );
540 #define TEST_CONVERSION_ONLY
542 #define TESTCASE_SPRINTF( x ) if ( strcmp( target, x ) == 0 ) {} \
543 else { TEST_RESULTS += 1; printf( "FAILED: " __FILE__ ", line %d - \"%s\" != %s\n", __LINE__, target, #x ); }
548 #include "printf_testcases.incl"