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.
13 /* Using an integer's bits as flags for both the conversion flags and length
16 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
17 width flags) into a combined field.
29 #define E_intmax 1<<10
31 #define E_ptrdiff 1<<12
32 #define E_intptr 1<<13
33 #define E_ldouble 1<<14
35 #define E_unsigned 1<<16
37 /* This macro delivers a given character to either a memory buffer or a stream,
38 depending on the contents of 'status' (struct _PDCLIB_status_t).
39 x - the character to be delivered
40 i - pointer to number of characters already delivered in this call
41 n - pointer to maximum number of characters to be delivered in this call
42 s - the buffer into which the character shall be delivered
47 if ( status->i < status->n ) { \
48 if ( status->stream != NULL ) \
49 putc( character, status->stream ); \
51 status->s[status->i] = character; \
57 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
59 /* At worst, we need two prefix characters (hex prefix). */
60 char preface[3] = "\0";
62 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
64 /* Octal / hexadecimal prefix for "%#" conversions */
65 preface[ preidx++ ] = '0';
66 if ( status->base == 16 )
68 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
73 /* Negative sign for negative values - at all times. */
74 preface[ preidx++ ] = '-';
76 else if ( ! ( status->flags & E_unsigned ) )
78 /* plus sign / extra space are only for unsigned conversions */
79 if ( status->flags & E_plus )
81 preface[ preidx++ ] = '+';
83 else if ( status->flags & E_space )
85 preface[ preidx++ ] = ' ';
89 size_t prec_pads = ( status->prec > status->current ) ? ( status->prec - status->current ) : 0;
90 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
92 /* Space padding is only done if no zero padding or left alignment
93 is requested. Leave space for any prefixes determined above.
95 /* The number of characters to be printed, plus prefixes if any. */
96 /* This line contained probably the most stupid, time-wasting bug
97 I've ever perpetrated. Greetings to Samface, DevL, and all
98 sceners at Breakpoint 2006.
100 size_t characters = preidx + ( ( status->current > status->prec ) ? status->current : status->prec );
101 if ( status->width > characters )
103 for ( size_t i = 0; i < status->width - characters; ++i )
110 /* Now we did the padding, do the prefixes (if any). */
112 while ( preface[ preidx ] != '\0' )
114 PUT( preface[ preidx++ ] );
117 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
119 /* If field is not left aligned, and zero padding is requested, do
122 while ( status->current < status->width )
128 /* Do the precision padding if necessary. */
129 for ( size_t i = 0; i < prec_pads; ++i )
137 /* This function recursively converts a given integer value to a character
138 stream. The conversion is done under the control of a given status struct
139 and written either to a character string or a stream, depending on that
140 same status struct. The status struct also keeps the function from exceeding
141 snprintf() limits, and enables any necessary padding / prefixing of the
142 output once the number of characters to be printed is known, which happens
143 at the lowermost recursion level.
145 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
147 /* Registering the character being printed at the end of the function here
148 already so it will be taken into account when the deepestmost recursion
149 does the prefix / padding stuff.
152 if ( ( value / status->base ) != 0 )
154 /* More digits to be done - recurse deeper */
155 int2base( value / status->base, status );
159 /* We reached the last digit, the deepest point of our recursion, and
160 only now know how long the number to be printed actually is. Now we
161 have to do the sign, prefix, width, and precision padding stuff
162 before printing the numbers while we resurface from the recursion.
164 intformat( value, status );
166 /* Recursion tail - print the current digit. */
168 int digit = value % status->base;
173 if ( status->flags & E_lower )
175 /* Lowercase letters. Same array used for strto...(). */
176 PUT( _PDCLIB_digits[ digit ] );
180 /* Uppercase letters. Array only used here, only 0-F. */
181 PUT( _PDCLIB_Xdigits[ digit ] );
187 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
189 const char * orig_spec = spec;
190 if ( *(++spec) == '%' )
192 /* %% -> print single '%' */
196 /* Initializing status structure */
203 /* First come 0..n flags */
209 /* left-aligned output */
210 status->flags |= E_minus;
214 /* positive numbers prefixed with '+' */
215 status->flags |= E_plus;
219 /* alternative format (leading 0x for hex, 0 for octal) */
220 status->flags |= E_alt;
224 /* positive numbers prefixed with ' ' */
225 status->flags |= E_space;
229 /* right-aligned padding done with '0' instead of ' ' */
230 status->flags |= E_zero;
234 /* not a flag, exit flag parsing */
235 status->flags |= E_done;
238 } while ( ! ( status->flags & E_done ) );
240 /* Optional field width */
243 /* Retrieve width value from argument stack */
244 int width = va_arg( status->arg, int );
247 status->flags |= E_minus;
248 status->width = abs( width );
252 status->width = width;
258 /* If a width is given, strtol() will return its value. If not given,
259 strtol() will return zero. In both cases, endptr will point to the
260 rest of the conversion specifier - just what we need.
262 status->width = (int)strtol( spec, (char**)&spec, 10 );
265 /* Optional precision */
271 /* Retrieve precision value from argument stack. A negative value
272 is as if no precision is given - as precision is initalized to
273 EOF (negative), there is no need for testing for negative here.
275 status->prec = va_arg( status->arg, int );
280 status->prec = (int)strtol( spec, &endptr, 10 );
281 if ( spec == endptr )
283 /* Decimal point but no number - bad conversion specifier. */
288 /* Having a precision cancels out any zero flag. */
289 status->flags ^= E_zero;
292 /* Optional length modifier
293 We step one character ahead in any case, and step back only if we find
294 there has been no length modifier (or step ahead another character if it
295 has been "hh" or "ll").
303 status->flags |= E_char;
309 status->flags |= E_short;
315 /* ll -> long long */
316 status->flags |= E_llong;
322 status->flags |= E_long;
326 /* j -> intmax_t, which might or might not be long long */
327 status->flags |= E_intmax;
330 /* z -> size_t, which might or might not be unsigned int */
331 status->flags |= E_size;
334 /* t -> ptrdiff_t, which might or might not be long */
335 status->flags |= E_ptrdiff;
338 /* L -> long double */
339 status->flags |= E_ldouble;
346 /* Conversion specifier */
356 status->flags |= E_unsigned;
360 status->flags |= E_unsigned;
364 status->flags |= ( E_lower | E_unsigned );
368 status->flags |= E_unsigned;
381 /* TODO: Flags, wide chars. */
382 PUT( va_arg( status->arg, int ) );
385 /* TODO: Flags, wide chars. */
387 char * s = va_arg( status->arg, char * );
395 /* TODO: E_long -> E_intptr */
397 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
401 int * val = va_arg( status->arg, int * );
406 /* No conversion specifier. Bad conversion. */
410 /* Do the actual output based on our findings */
411 if ( status->base != 0 )
413 /* Integer conversions */
414 /* TODO: Check for invalid flag combinations. */
415 if ( status->flags & E_unsigned )
418 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
421 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
424 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
427 value = (uintmax_t)va_arg( status->arg, unsigned int );
430 value = (uintmax_t)va_arg( status->arg, unsigned long );
433 value = (uintmax_t)va_arg( status->arg, unsigned long long );
436 value = (uintmax_t)va_arg( status->arg, size_t );
440 /* FIXME: The if clause means one-digit values do not get formatted */
441 /* Was introduced originally to get value to "safe" levels re. uintmax_t. */
442 if ( ( value / status->base ) != 0 )
444 int2base( (intmax_t)(value / status->base), status );
448 intformat( (intmax_t)value, status );
450 int digit = value % status->base;
455 if ( status->flags & E_lower )
457 PUT( _PDCLIB_digits[ digit ] );
461 PUT( _PDCLIB_Xdigits[ digit ] );
466 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
469 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
472 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
475 int2base( (intmax_t)va_arg( status->arg, int ), status );
478 int2base( (intmax_t)va_arg( status->arg, long ), status );
481 int2base( (intmax_t)va_arg( status->arg, long long ), status );
484 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
487 int2base( va_arg( status->arg, intmax_t ), status );
491 if ( status->flags & E_minus )
493 while ( status->current < status->width )
499 if ( status->i >= status->n && status->n > 0 )
501 status->s[status->n - 1] = '\0';
508 #define _PDCLIB_FILEID "_PDCLIB/print.c"
509 #define _PDCLIB_STRINGIO
511 #include <_PDCLIB_test.h>
513 static int testprintf( char * buffer, const char * format, ... )
515 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
516 struct _PDCLIB_status_t status;
525 status.stream = NULL;
526 va_start( status.arg, format );
527 memset( buffer, '\0', 100 );
528 if ( *(_PDCLIB_print( format, &status )) != '\0' )
530 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
533 va_end( status.arg );
537 #define TEST_CONVERSION_ONLY
542 #include "printf_testcases.h"