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.
17 /* Using an integer's bits as flags for both the conversion flags and length
20 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
21 width flags) into a combined field.
33 #define E_intmax 1<<10
35 #define E_ptrdiff 1<<12
36 #define E_intptr 1<<13
37 #define E_ldouble 1<<14
39 #define E_unsigned 1<<16
41 /* This macro delivers a given character to either a memory buffer or a stream,
42 depending on the contents of 'status' (struct _PDCLIB_status_t).
43 x - the character to be delivered
44 i - pointer to number of characters already delivered in this call
45 n - pointer to maximum number of characters to be delivered in this call
46 s - the buffer into which the character shall be delivered
51 if ( status->i < status->n ) { \
52 if ( status->stream != NULL ) \
53 putc( character, status->stream ); \
55 status->s[status->i] = character; \
61 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
63 /* At worst, we need two prefix characters (hex prefix). */
64 char preface[3] = "\0";
66 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
68 /* Octal / hexadecimal prefix for "%#" conversions */
69 preface[ preidx++ ] = '0';
70 if ( status->base == 16 )
72 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
77 /* Negative sign for negative values - at all times. */
78 preface[ preidx++ ] = '-';
80 else if ( ! ( status->flags & E_unsigned ) )
82 /* plus sign / extra space are only for unsigned conversions */
83 if ( status->flags & E_plus )
85 preface[ preidx++ ] = '+';
87 else if ( status->flags & E_space )
89 preface[ preidx++ ] = ' ';
93 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
94 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
96 /* Space padding is only done if no zero padding or left alignment
97 is requested. Leave space for any prefixes determined above.
99 /* The number of characters to be printed, plus prefixes if any. */
100 /* This line contained probably the most stupid, time-wasting bug
101 I've ever perpetrated. Greetings to Samface, DevL, and all
102 sceners at Breakpoint 2006.
104 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
105 if ( status->width > characters )
107 for ( size_t i = 0; i < status->width - characters; ++i )
114 /* Now we did the padding, do the prefixes (if any). */
116 while ( preface[ preidx ] != '\0' )
118 PUT( preface[ preidx++ ] );
121 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
123 /* If field is not left aligned, and zero padding is requested, do
126 while ( status->current < status->width )
132 /* Do the precision padding if necessary. */
133 for ( size_t i = 0; i < prec_pads; ++i )
141 /* This function recursively converts a given integer value to a character
142 stream. The conversion is done under the control of a given status struct
143 and written either to a character string or a stream, depending on that
144 same status struct. The status struct also keeps the function from exceeding
145 snprintf() limits, and enables any necessary padding / prefixing of the
146 output once the number of characters to be printed is known, which happens
147 at the lowermost recursion level.
149 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
151 /* Registering the character being printed at the end of the function here
152 already so it will be taken into account when the deepestmost recursion
153 does the prefix / padding stuff.
156 if ( ( value / status->base ) != 0 )
158 /* More digits to be done - recurse deeper */
159 int2base( value / status->base, status );
163 /* We reached the last digit, the deepest point of our recursion, and
164 only now know how long the number to be printed actually is. Now we
165 have to do the sign, prefix, width, and precision padding stuff
166 before printing the numbers while we resurface from the recursion.
168 intformat( value, status );
170 /* Recursion tail - print the current digit. */
172 int digit = value % status->base;
177 if ( status->flags & E_lower )
179 /* Lowercase letters. Same array used for strto...(). */
180 PUT( _PDCLIB_digits[ digit ] );
184 /* Uppercase letters. Array only used here, only 0-F. */
185 PUT( _PDCLIB_Xdigits[ digit ] );
191 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
193 const char * orig_spec = spec;
194 if ( *(++spec) == '%' )
196 /* %% -> print single '%' */
200 /* Initializing status structure */
207 /* First come 0..n flags */
213 /* left-aligned output */
214 status->flags |= E_minus;
218 /* positive numbers prefixed with '+' */
219 status->flags |= E_plus;
223 /* alternative format (leading 0x for hex, 0 for octal) */
224 status->flags |= E_alt;
228 /* positive numbers prefixed with ' ' */
229 status->flags |= E_space;
233 /* right-aligned padding done with '0' instead of ' ' */
234 status->flags |= E_zero;
238 /* not a flag, exit flag parsing */
239 status->flags |= E_done;
242 } while ( ! ( status->flags & E_done ) );
244 /* Optional field width */
247 /* Retrieve width value from argument stack */
248 int width = va_arg( status->arg, int );
251 status->flags |= E_minus;
252 status->width = abs( width );
256 status->width = width;
262 /* If a width is given, strtol() will return its value. If not given,
263 strtol() will return zero. In both cases, endptr will point to the
264 rest of the conversion specifier - just what we need.
266 status->width = (int)strtol( spec, (char**)&spec, 10 );
269 /* Optional precision */
275 /* Retrieve precision value from argument stack. A negative value
276 is as if no precision is given - as precision is initalized to
277 EOF (negative), there is no need for testing for negative here.
279 status->prec = va_arg( status->arg, int );
284 status->prec = (int)strtol( spec, &endptr, 10 );
285 if ( spec == endptr )
287 /* Decimal point but no number - bad conversion specifier. */
292 /* Having a precision cancels out any zero flag. */
293 status->flags ^= E_zero;
296 /* Optional length modifier
297 We step one character ahead in any case, and step back only if we find
298 there has been no length modifier (or step ahead another character if it
299 has been "hh" or "ll").
307 status->flags |= E_char;
313 status->flags |= E_short;
319 /* ll -> long long */
320 status->flags |= E_llong;
326 status->flags |= E_long;
330 /* j -> intmax_t, which might or might not be long long */
331 status->flags |= E_intmax;
334 /* z -> size_t, which might or might not be unsigned int */
335 status->flags |= E_size;
338 /* t -> ptrdiff_t, which might or might not be long */
339 status->flags |= E_ptrdiff;
342 /* L -> long double */
343 status->flags |= E_ldouble;
350 /* Conversion specifier */
360 status->flags |= E_unsigned;
364 status->flags |= E_unsigned;
368 status->flags |= ( E_lower | E_unsigned );
372 status->flags |= E_unsigned;
385 /* TODO: Flags, wide chars. */
386 PUT( va_arg( status->arg, int ) );
389 /* TODO: Flags, wide chars. */
391 char * s = va_arg( status->arg, char * );
399 /* TODO: E_long -> E_intptr */
401 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
405 int * val = va_arg( status->arg, int * );
410 /* No conversion specifier. Bad conversion. */
414 /* Do the actual output based on our findings */
415 if ( status->base != 0 )
417 /* Integer conversions */
418 /* TODO: Check for invalid flag combinations. */
419 if ( status->flags & E_unsigned )
422 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
425 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
428 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
431 value = (uintmax_t)va_arg( status->arg, unsigned int );
434 value = (uintmax_t)va_arg( status->arg, unsigned long );
437 value = (uintmax_t)va_arg( status->arg, unsigned long long );
440 value = (uintmax_t)va_arg( status->arg, size_t );
444 /* FIXME: The if clause means one-digit values do not get formatted */
445 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
446 if ( ( value / status->base ) != 0 )
448 int2base( (intmax_t)(value / status->base), status );
452 intformat( (intmax_t)value, status );
454 int digit = value % status->base;
459 if ( status->flags & E_lower )
461 PUT( _PDCLIB_digits[ digit ] );
465 PUT( _PDCLIB_Xdigits[ digit ] );
470 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
473 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
476 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
479 int2base( (intmax_t)va_arg( status->arg, int ), status );
482 int2base( (intmax_t)va_arg( status->arg, long ), status );
485 int2base( (intmax_t)va_arg( status->arg, long long ), status );
488 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
491 int2base( va_arg( status->arg, intmax_t ), status );
495 if ( status->flags & E_minus )
497 while ( status->current < status->width )
503 if ( status->i >= status->n && status->n > 0 )
505 status->s[status->n - 1] = '\0';
514 #define _PDCLIB_FILEID "_PDCLIB/print.c"
515 #define _PDCLIB_STRINGIO
517 #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 );
545 #define TEST_CONVERSION_ONLY
551 #include "printf_testcases.h"