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
28 #define E_intmax 1<<10
30 #define E_ptrdiff 1<<12
31 #define E_intptr 1<<13
32 #define E_double 1<<14
34 #define E_unsigned 1<<16
36 /* This macro delivers a given character to either a memory buffer or a stream,
37 depending on the contents of 'status' (struct _PDCLIB_status_t).
38 x - the character to be delivered
39 i - pointer to number of characters already delivered in this call
40 n - pointer to maximum number of characters to be delivered in this call
41 s - the buffer into which the character shall be delivered
43 #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 )
45 /* This function recursively converts a given integer value to a given base
46 into a character string. Persistent information - like the number of digits
47 parsed so far - is recorded in a struct _PDCLIB_status_t, which allows to
48 avoid overwriting snprintf() limits, and enables the function to do the
49 necessary padding / prefixing of the character string eventually printed.
51 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
53 /* Registering the character being printed at the end of the function here
54 already so it will be taken into account when the deepestmost recursion
55 does the prefix / padding stuff.
58 if ( ( value / status->base ) != 0 )
60 /* More digits to be done - recurse deeper */
61 int2base( value / status->base, status );
65 /* We reached the last digit, the deepest point of our recursion, and
66 only now know how long the number to be printed actually is. Now we
67 have to do the sign, prefix, width, and precision padding stuff
68 before printing the numbers while we resurface from the recursion.
70 /* At worst, we need two prefix characters (hex prefix). */
71 char preface[3] = "\0";
73 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
75 /* Octal / hexadecimal prefix for "%#" conversions */
76 preface[ preidx++ ] = '0';
77 if ( status->base == 16 )
79 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
84 /* Negative sign for negative values - at all times. */
85 preface[ preidx++ ] = '-';
87 else if ( ! ( status->flags & E_unsigned ) )
89 /* plus sign / extra space are only for unsigned conversions */
90 if ( status->flags & E_plus )
92 preface[ preidx++ ] = '+';
94 else if ( status->flags & E_space )
96 preface[ preidx++ ] = ' ';
100 size_t prec_pads = ( status->prec > status->this ) ? ( status->prec - status->this ) : 0;
101 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
103 /* Space padding is only done if no zero padding or left alignment
104 is requested. Leave space for any prefixes determined above.
106 /* The number of characters to be printed, plus prefixes if any. */
107 /* This line contained probably the most stupid, time-wasting bug
108 I've ever perpetrated. Greetings to Samface, DevL, and all
109 sceners at Breakpoint 2006.
111 size_t characters = preidx + ( ( status->this > status->prec ) ? status->this : status->prec );
112 if ( status->width > characters )
114 for ( int i = 0; i < status->width - characters; ++i )
121 /* Now we did the padding, do the prefixes (if any). */
123 while ( preface[ preidx ] != '\0' )
125 DELIVER( preface[ preidx++ ] );
128 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
130 /* If field is not left aligned, and zero padding is requested, do
133 while ( status->this < status->width )
139 /* Do the precision padding if necessary. */
140 for ( int i = 0; i < prec_pads; ++i )
146 /* Recursion tail - print the current digit. */
148 int digit = value % status->base;
153 if ( status->flags & E_lower )
155 /* Lowercase letters. Same array used for strto...(). */
156 DELIVER( _PDCLIB_digits[ digit ] );
160 /* Uppercase letters. Array only used here, only 0-F. */
161 DELIVER( _PDCLIB_Xdigits[ digit ] );
166 /* This function is to be called with spec pointing to the leading '%' of a
167 printf() conversion specifier.
169 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
171 const char * orig_spec = spec;
172 if ( *(++spec) == '%' )
177 /* Initializing status structure */
184 /* First come 0..n flags */
190 status->flags |= E_minus;
194 status->flags |= E_plus;
198 status->flags |= E_alt;
202 status->flags |= E_space;
206 status->flags |= E_zero;
210 status->flags |= E_done;
213 } while ( ! ( status->flags & E_done ) );
215 /* Optional field width */
218 /* Retrieve width value from argument stack */
219 if ( ( status->width = va_arg( status->arg, int ) ) < 0 )
221 /* Negative value is '-' flag plus absolute value */
222 status->flags |= E_minus;
229 /* If a width is given, strtol() will return its value. If not given,
230 strtol() will return zero. In both cases, endptr will point to the
231 rest of the conversion specifier - just what we need.
233 status->width = (int)strtol( spec, (char**)&spec, 10 );
236 /* Optional precision */
242 /* Retrieve precision value from argument stack. A negative value
243 is as if no precision is given - as precision is initalized to
244 EOF (negative), there is no need for testing for negative here.
246 status->prec = va_arg( status->arg, int );
251 status->prec = (int)strtol( spec, &endptr, 10 );
252 if ( spec == endptr )
254 /* Decimal point but no number - bad conversion specifier. */
259 /* Having a precision cancels out any zero flag. */
260 status->flags ^= E_zero;
263 /* Optional length modifier
264 We step one character ahead in any case, and step back only if we find
265 there has been no length modifier (or step ahead another character if it
266 has been "hh" or "ll").
273 status->flags |= E_char;
278 status->flags |= E_short;
284 status->flags |= E_llong;
289 status->flags |= E_long;
293 status->flags |= E_intmax;
296 status->flags |= E_size;
299 status->flags |= E_ptrdiff;
302 status->flags |= E_double;
309 /* Conversion specifier */
319 status->flags |= E_unsigned;
323 status->flags |= E_unsigned;
327 status->flags |= ( E_lower | E_unsigned );
331 status->flags |= E_unsigned;
344 /* TODO: Flags, wide chars. */
345 DELIVER( va_arg( status->arg, int ) );
348 /* TODO: Flags, wide chars. */
350 char * s = va_arg( status->arg, char * );
358 /* TODO: E_long -> E_intptr */
360 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
364 int * val = va_arg( status->arg, int * );
369 /* No conversion specifier. Bad conversion. */
373 /* Do the actual output based on our findings */
374 if ( status->base != 0 )
376 /* Integer conversions */
377 /* TODO: Check for invalid flag combinations. */
378 if ( status->flags & E_unsigned )
381 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
384 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
387 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
390 value = (uintmax_t)va_arg( status->arg, unsigned int );
393 value = (uintmax_t)va_arg( status->arg, unsigned long );
396 value = (uintmax_t)va_arg( status->arg, unsigned long long );
399 value = (uintmax_t)va_arg( status->arg, size_t );
403 if ( ( value / status->base ) != 0 )
405 int2base( (intmax_t)(value / status->base), status );
407 int digit = value % status->base;
412 if ( status->flags & E_lower )
414 DELIVER( _PDCLIB_digits[ digit ] );
418 DELIVER( _PDCLIB_Xdigits[ digit ] );
423 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
426 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
429 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
432 int2base( (intmax_t)va_arg( status->arg, int ), status );
435 int2base( (intmax_t)va_arg( status->arg, long ), status );
438 int2base( (intmax_t)va_arg( status->arg, long long ), status );
441 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
444 int2base( va_arg( status->arg, intmax_t ), status );
448 if ( status->flags & E_minus )
450 while ( status->this < status->width )
456 if ( status->i >= status->n )
458 status->s[status->n - 1] = '\0';
465 #include <_PDCLIB_test.h>
469 TESTCASE( NO_TESTDRIVER );