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.
12 /* Using an integer's bits as flags for both the conversion flags and length
25 #define E_intmax 1<<10
27 #define E_ptrdiff 1<<12
28 #define E_intptr 1<<13
29 #define E_double 1<<14
31 #define E_unsigned 1<<16
33 /* This macro delivers a given character to either a memory buffer or a stream,
34 depending on the contents of 'status' (struct _PDCLIB_status_t).
35 x - the character to be delivered
36 i - pointer to number of characters already delivered in this call
37 n - pointer to maximum number of characters to be delivered in this call
38 s - the buffer into which the character shall be delivered
40 #define DELIVER( x ) do { if ( status->i < status->n ) { if ( status->stream != NULL ) putc( x, status->stream ); else status->s[status->i] = x; } ++(status->i); } while ( 0 )
42 /* This function recursively converts a given integer value to a given base
43 into a character string. Persistent information - like the number of digits
44 parsed so far - is recorded in a struct _PDCLIB_status_t, which allows to
45 avoid overwriting snprintf() limits, and enables the function to do the
46 necessary padding / prefixing of the character string eventually printed.
48 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
50 /* Registering the character being printed at the end of the function here
51 already so it will be taken into account when the deepestmost recursion
52 does the prefix / padding stuff.
55 if ( ( value / status->base ) != 0 )
57 /* More digits to be done - recurse deeper */
58 int2base( value / status->base, status );
62 /* We reached the last digit, the deepest point of our recursion, and
63 only now know how long the number to be printed actually is. Now we
64 have to do the sign, prefix, width, and precision padding stuff
65 before printing the numbers while we resurface from the recursion.
67 /* At worst, we need two prefix characters (hex prefix). */
68 char preface[3] = "\0";
70 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
72 /* Octal / hexadecimal prefix for "%#" conversions */
73 preface[ preidx++ ] = '0';
74 if ( status->base == 16 )
76 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
81 /* Negative sign for negative values - at all times. */
82 preface[ preidx++ ] = '-';
84 else if ( ! ( status->flags & E_unsigned ) )
86 /* plus sign / extra space are only for unsigned conversions */
87 if ( status->flags & E_plus )
89 preface[ preidx++ ] = '+';
91 else if ( status->flags & E_space )
93 preface[ preidx++ ] = ' ';
97 size_t prec_pads = ( status->prec > status->this ) ? ( status->prec - status->this ) : 0;
98 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
100 /* Space padding is only done if no zero padding or left alignment
101 is requested. Leave space for any prefixes determined above.
103 /* The number of characters to be printed, plus prefixes if any. */
104 /* This line contained probably the most stupid, time-wasting bug
105 I've ever perpetrated. Greetings to Samface, DevL, and all
106 sceners at Breakpoint 2006.
108 size_t characters = preidx + ( ( status->this > status->prec ) ? status->this : status->prec );
109 if ( status->width > characters )
111 for ( int i = 0; i < status->width - characters; ++i )
118 /* Now we did the padding, do the prefixes (if any). */
120 while ( preface[ preidx ] != '\0' )
122 DELIVER( preface[ preidx++ ] );
125 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
127 /* If field is not left aligned, and zero padding is requested, do
130 while ( status->this < status->width )
136 /* Do the precision padding if necessary. */
137 for ( int i = 0; i < prec_pads; ++i )
143 /* Recursion tail - print the current digit. */
145 int digit = value % status->base;
150 if ( status->flags & E_lower )
152 /* Lowercase letters. Same array used for strto...(). */
153 DELIVER( _PDCLIB_digits[ digit ] );
157 /* Uppercase letters. Array only used here, only 0-F. */
158 DELIVER( _PDCLIB_Xdigits[ digit ] );
163 /* This function is to be called with spec pointing to the leading '%' of a
164 printf() conversion specifier.
166 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
168 const char * orig_spec = spec;
169 if ( *(++spec) == '%' )
174 /* Initializing status structure */
181 /* First come 0..n flags */
187 status->flags |= E_minus;
191 status->flags |= E_plus;
195 status->flags |= E_alt;
199 status->flags |= E_space;
203 status->flags |= E_zero;
207 status->flags |= E_done;
210 } while ( ! ( status->flags & E_done ) );
212 /* Optional field width */
215 /* Retrieve width value from argument stack */
216 if ( ( status->width = va_arg( status->arg, int ) ) < 0 )
218 /* Negative value is '-' flag plus absolute value */
219 status->flags |= E_minus;
226 /* If a width is given, strtol() will return its value. If not given,
227 strtol() will return zero. In both cases, endptr will point to the
228 rest of the conversion specifier - just what we need.
230 status->width = (int)strtol( spec, (char**)&spec, 10 );
233 /* Optional precision */
239 /* Retrieve precision value from argument stack. A negative value
240 is as if no precision is given - as precision is initalized to
241 EOF (negative), there is no need for testing for negative here.
243 status->prec = va_arg( status->arg, int );
248 status->prec = (int)strtol( spec, &endptr, 10 );
249 if ( spec == endptr )
251 /* Decimal point but no number - bad conversion specifier. */
256 /* Having a precision cancels out any zero flag. */
257 status->flags ^= E_zero;
260 /* Optional length modifier
261 We step one character ahead in any case, and step back only if we find
262 there has been no length modifier (or step ahead another character if it
263 has been "hh" or "ll").
270 status->flags |= E_char;
275 status->flags |= E_short;
281 status->flags |= E_llong;
286 status->flags |= E_long;
290 status->flags |= E_intmax;
293 status->flags |= E_size;
296 status->flags |= E_ptrdiff;
299 status->flags |= E_double;
306 /* Conversion specifier */
316 status->flags |= E_unsigned;
320 status->flags |= E_unsigned;
324 status->flags |= ( E_lower | E_unsigned );
328 status->flags |= E_unsigned;
341 /* TODO: Flags, wide chars. */
342 DELIVER( va_arg( status->arg, int ) );
345 /* TODO: Flags, wide chars. */
347 char * s = va_arg( status->arg, char * );
355 /* TODO: E_long -> E_intptr */
357 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
361 int * val = va_arg( status->arg, int * );
366 /* No conversion specifier. Bad conversion. */
370 /* Do the actual output based on our findings */
371 if ( status->base != 0 )
373 /* Integer conversions */
374 /* TODO: Check for invalid flag combinations. */
375 if ( status->flags & E_unsigned )
378 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
381 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
384 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
387 value = (uintmax_t)va_arg( status->arg, unsigned int );
390 value = (uintmax_t)va_arg( status->arg, unsigned long );
393 value = (uintmax_t)va_arg( status->arg, unsigned long long );
396 value = (uintmax_t)va_arg( status->arg, size_t );
400 if ( ( value / status->base ) != 0 )
402 int2base( (intmax_t)(value / status->base), status );
404 int digit = value % status->base;
409 if ( status->flags & E_lower )
411 DELIVER( _PDCLIB_digits[ digit ] );
415 DELIVER( _PDCLIB_Xdigits[ digit ] );
420 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
423 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
426 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
429 int2base( (intmax_t)va_arg( status->arg, int ), status );
432 int2base( (intmax_t)va_arg( status->arg, long ), status );
435 int2base( (intmax_t)va_arg( status->arg, long long ), status );
438 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
441 int2base( va_arg( status->arg, intmax_t ), status );
445 if ( status->flags & E_minus )
447 while ( status->this < status->width )
453 if ( status->i >= status->n )
455 status->s[status->n - 1] = '\0';