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
46 #define DELIVER( x ) \
49 if ( status->i < status->n ) { \
50 if ( status->stream != NULL ) \
51 putc( character, status->stream ); \
53 status->s[status->i] = character; \
59 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
61 /* At worst, we need two prefix characters (hex prefix). */
62 char preface[3] = "\0";
64 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
66 /* Octal / hexadecimal prefix for "%#" conversions */
67 preface[ preidx++ ] = '0';
68 if ( status->base == 16 )
70 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
75 /* Negative sign for negative values - at all times. */
76 preface[ preidx++ ] = '-';
78 else if ( ! ( status->flags & E_unsigned ) )
80 /* plus sign / extra space are only for unsigned conversions */
81 if ( status->flags & E_plus )
83 preface[ preidx++ ] = '+';
85 else if ( status->flags & E_space )
87 preface[ preidx++ ] = ' ';
91 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
92 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
94 /* Space padding is only done if no zero padding or left alignment
95 is requested. Leave space for any prefixes determined above.
97 /* The number of characters to be printed, plus prefixes if any. */
98 /* This line contained probably the most stupid, time-wasting bug
99 I've ever perpetrated. Greetings to Samface, DevL, and all
100 sceners at Breakpoint 2006.
102 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
103 if ( status->width > characters )
105 for ( size_t i = 0; i < status->width - characters; ++i )
112 /* Now we did the padding, do the prefixes (if any). */
114 while ( preface[ preidx ] != '\0' )
116 DELIVER( preface[ preidx++ ] );
119 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
121 /* If field is not left aligned, and zero padding is requested, do
124 while ( status->current < status->width )
130 /* Do the precision padding if necessary. */
131 for ( size_t i = 0; i < prec_pads; ++i )
139 /* This function recursively converts a given integer value to a character
140 stream. The conversion is done under the control of a given status struct
141 and written either to a character string or a stream, depending on that
142 same status struct. The status struct also keeps the function from exceeding
143 snprintf() limits, and enables any necessary padding / prefixing of the
144 output once the number of characters to be printed is known, which happens
145 at the lowermost recursion level.
147 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
149 /* Registering the character being printed at the end of the function here
150 already so it will be taken into account when the deepestmost recursion
151 does the prefix / padding stuff.
154 if ( ( value / status->base ) != 0 )
156 /* More digits to be done - recurse deeper */
157 int2base( value / status->base, status );
161 /* We reached the last digit, the deepest point of our recursion, and
162 only now know how long the number to be printed actually is. Now we
163 have to do the sign, prefix, width, and precision padding stuff
164 before printing the numbers while we resurface from the recursion.
166 intformat( value, status );
168 /* Recursion tail - print the current digit. */
170 int digit = value % status->base;
175 if ( status->flags & E_lower )
177 /* Lowercase letters. Same array used for strto...(). */
178 DELIVER( _PDCLIB_digits[ digit ] );
182 /* Uppercase letters. Array only used here, only 0-F. */
183 DELIVER( _PDCLIB_Xdigits[ digit ] );
189 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
191 const char * orig_spec = spec;
192 if ( *(++spec) == '%' )
194 /* %% -> print single '%' */
198 /* Initializing status structure */
205 /* First come 0..n flags */
211 /* left-aligned output */
212 status->flags |= E_minus;
216 /* positive numbers prefixed with '+' */
217 status->flags |= E_plus;
221 /* alternative format (leading 0x for hex, 0 for octal) */
222 status->flags |= E_alt;
226 /* positive numbers prefixed with ' ' */
227 status->flags |= E_space;
231 /* right-aligned padding done with '0' instead of ' ' */
232 status->flags |= E_zero;
236 /* not a flag, exit flag parsing */
237 status->flags |= E_done;
240 } while ( ! ( status->flags & E_done ) );
242 /* Optional field width */
245 /* Retrieve width value from argument stack */
246 int width = va_arg( status->arg, int );
249 status->flags |= E_minus;
250 status->width = abs( width );
254 status->width = width;
260 /* If a width is given, strtol() will return its value. If not given,
261 strtol() will return zero. In both cases, endptr will point to the
262 rest of the conversion specifier - just what we need.
264 status->width = (int)strtol( spec, (char**)&spec, 10 );
267 /* Optional precision */
273 /* Retrieve precision value from argument stack. A negative value
274 is as if no precision is given - as precision is initalized to
275 EOF (negative), there is no need for testing for negative here.
277 status->prec = va_arg( status->arg, int );
282 status->prec = (int)strtol( spec, &endptr, 10 );
283 if ( spec == endptr )
285 /* Decimal point but no number - bad conversion specifier. */
290 /* Having a precision cancels out any zero flag. */
291 status->flags ^= E_zero;
294 /* Optional length modifier
295 We step one character ahead in any case, and step back only if we find
296 there has been no length modifier (or step ahead another character if it
297 has been "hh" or "ll").
305 status->flags |= E_char;
311 status->flags |= E_short;
317 /* ll -> long long */
318 status->flags |= E_llong;
324 status->flags |= E_long;
328 /* j -> intmax_t, which might or might not be long long */
329 status->flags |= E_intmax;
332 /* z -> size_t, which might or might not be unsigned int */
333 status->flags |= E_size;
336 /* t -> ptrdiff_t, which might or might not be long */
337 status->flags |= E_ptrdiff;
340 /* L -> long double */
341 status->flags |= E_ldouble;
348 /* Conversion specifier */
358 status->flags |= E_unsigned;
362 status->flags |= E_unsigned;
366 status->flags |= ( E_lower | E_unsigned );
370 status->flags |= E_unsigned;
383 /* TODO: Flags, wide chars. */
384 DELIVER( va_arg( status->arg, int ) );
387 /* TODO: Flags, wide chars. */
389 char * s = va_arg( status->arg, char * );
397 /* TODO: E_long -> E_intptr */
399 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
403 int * val = va_arg( status->arg, int * );
408 /* No conversion specifier. Bad conversion. */
412 /* Do the actual output based on our findings */
413 if ( status->base != 0 )
415 /* Integer conversions */
416 /* TODO: Check for invalid flag combinations. */
417 if ( status->flags & E_unsigned )
420 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
423 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
426 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
429 value = (uintmax_t)va_arg( status->arg, unsigned int );
432 value = (uintmax_t)va_arg( status->arg, unsigned long );
435 value = (uintmax_t)va_arg( status->arg, unsigned long long );
438 value = (uintmax_t)va_arg( status->arg, size_t );
442 /* FIXME: The if clause means one-digit values do not get formatted */
443 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
444 if ( ( value / status->base ) != 0 )
446 int2base( (intmax_t)(value / status->base), status );
450 intformat( (intmax_t)value, status );
452 int digit = value % status->base;
457 if ( status->flags & E_lower )
459 DELIVER( _PDCLIB_digits[ digit ] );
463 DELIVER( _PDCLIB_Xdigits[ digit ] );
468 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
471 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
474 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
477 int2base( (intmax_t)va_arg( status->arg, int ), status );
480 int2base( (intmax_t)va_arg( status->arg, long ), status );
483 int2base( (intmax_t)va_arg( status->arg, long long ), status );
486 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
489 int2base( va_arg( status->arg, intmax_t ), status );
493 if ( status->flags & E_minus )
495 while ( status->current < status->width )
501 if ( status->i >= status->n && status->n > 0 )
503 status->s[status->n - 1] = '\0';
510 #define _PDCLIB_FILEID "_PDCLIB/print.c"
511 #define SPRINTF_FUNCTION
512 #include <_PDCLIB_test.h>
517 static int testprintf( char * buffer, const char * format, ... )
519 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
520 struct _PDCLIB_status_t status;
529 status.stream = NULL;
530 va_start( status.arg, format );
531 memset( buffer, '\0', 100 );
532 if ( *(_PDCLIB_print( format, &status )) != '\0' )
534 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
537 va_end( status.arg );
541 #define TEST_CONVERSION_ONLY
546 #include "printf_testcases.incl"