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.
16 /* Using an integer's bits as flags for both the conversion flags and length
19 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
20 width flags) into a combined field.
22 #define E_minus (1<<0)
25 #define E_space (1<<3)
30 #define E_short (1<<7)
32 #define E_llong (1<<9)
33 #define E_intmax (1<<10)
34 #define E_size (1<<11)
35 #define E_ptrdiff (1<<12)
36 #define E_pointer (1<<13)
38 #define E_ldouble (1<<14)
40 #define E_lower (1<<15)
41 #define E_unsigned (1<<16)
43 /* This macro delivers a given character to either a memory buffer or a stream,
44 depending on the contents of 'status' (struct _PDCLIB_status_t).
45 x - the character to be delivered
46 i - pointer to number of characters already delivered in this call
47 n - pointer to maximum number of characters to be delivered in this call
48 s - the buffer into which the character shall be delivered
53 if ( status->i < status->n ) { \
54 if ( status->stream != NULL ) \
55 putc( character, status->stream ); \
57 status->s[status->i] = character; \
63 static void intformat( intmax_t value, struct _PDCLIB_status_t * status )
65 if ( status->prec < 0 )
69 /* At worst, we need two prefix characters (hex prefix). */
70 char preface[3] = "\0";
72 if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) && ( value != 0 ) )
74 /* Octal / hexadecimal prefix for "%#" conversions */
75 preface[ preidx++ ] = '0';
76 if ( status->base == 16 )
78 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
83 /* Negative sign for negative values - at all times. */
84 preface[ preidx++ ] = '-';
86 else if ( ! ( status->flags & E_unsigned ) )
88 /* plus sign / extra space are only for unsigned conversions */
89 if ( status->flags & E_plus )
91 preface[ preidx++ ] = '+';
93 else if ( status->flags & E_space )
95 preface[ preidx++ ] = ' ';
99 /* At this point, status->current has the number of digits queued up.
100 Determine if we have a precision requirement to pad those.
102 size_t prec_pads = ( (_PDCLIB_size_t)status->prec > status->current ) ? ( (_PDCLIB_size_t)status->prec - status->current ) : 0;
103 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
105 /* Space padding is only done if no zero padding or left alignment
106 is requested. Calculate the number of characters that WILL be
107 printed, including any prefixes determined above.
109 /* The number of characters to be printed, plus prefixes if any. */
110 /* This line contained probably the most stupid, time-wasting bug
111 I've ever perpetrated. Greetings to Samface, DevL, and all
112 sceners at Breakpoint 2006.
114 size_t characters = preidx + ( ( status->current > (_PDCLIB_size_t)status->prec ) ? status->current : (_PDCLIB_size_t)status->prec );
115 if ( status->width > characters )
117 for ( size_t i = 0; i < status->width - characters; ++i )
124 /* Now we did the padding, do the prefixes (if any). */
126 while ( preface[ preidx ] != '\0' )
128 PUT( preface[ preidx++ ] );
131 /* Do the precision padding if necessary. */
132 while ( prec_pads-- > 0 )
137 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
139 /* If field is not left aligned, and zero padding is requested, do
142 while ( status->current < status->width )
152 /* This function recursively converts a given integer value to a character
153 stream. The conversion is done under the control of a given status struct
154 and written either to a character string or a stream, depending on that
155 same status struct. The status struct also keeps the function from exceeding
156 snprintf() limits, and enables any necessary padding / prefixing of the
157 output once the number of characters to be printed is known, which happens
158 at the lowermost recursion level.
160 static void int2base( intmax_t value, struct _PDCLIB_status_t * status )
162 /* Registering the character being printed at the end of the function here
163 already so it will be taken into account when the deepestmost recursion
164 does the prefix / padding stuff.
167 if ( ( value / status->base ) != 0 )
169 /* More digits to be done - recurse deeper */
170 int2base( value / status->base, status );
174 /* We reached the last digit, the deepest point of our recursion, and
175 only now know how long the number to be printed actually is. Now we
176 have to do the sign, prefix, width, and precision padding stuff
177 before printing the numbers while we resurface from the recursion.
179 intformat( value, status );
181 /* Recursion tail - print the current digit. */
183 int digit = value % status->base;
188 if ( status->flags & E_lower )
190 /* Lowercase letters. Same array used for strto...(). */
191 PUT( _PDCLIB_digits[ digit ] );
195 /* Uppercase letters. Array only used here, only 0-F. */
196 PUT( _PDCLIB_Xdigits[ digit ] );
202 static void stringformat( const char * s, struct _PDCLIB_status_t * status )
204 if ( status->flags & E_char )
210 if ( status->prec < 0 )
212 status->prec = strlen( s );
216 for ( int i = 0; i < status->prec; ++i )
226 if ( ! ( status->flags & E_minus ) && ( status->width > (_PDCLIB_size_t)status->prec ) )
228 while ( status->current < ( status->width - status->prec ) )
234 while ( status->prec > 0 )
240 if ( status->flags & E_minus )
242 while ( status->width > status->current )
251 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
253 const char * orig_spec = spec;
254 if ( *(++spec) == '%' )
256 /* %% -> print single '%' */
260 /* Initializing status structure */
267 /* First come 0..n flags */
273 /* left-aligned output */
274 status->flags |= E_minus;
278 /* positive numbers prefixed with '+' */
279 status->flags |= E_plus;
283 /* alternative format (leading 0x for hex, 0 for octal) */
284 status->flags |= E_alt;
288 /* positive numbers prefixed with ' ' */
289 status->flags |= E_space;
293 /* right-aligned padding done with '0' instead of ' ' */
294 status->flags |= E_zero;
298 /* not a flag, exit flag parsing */
299 status->flags |= E_done;
302 } while ( ! ( status->flags & E_done ) );
304 /* Optional field width */
307 /* Retrieve width value from argument stack */
308 int width = va_arg( status->arg, int );
311 status->flags |= E_minus;
312 status->width = abs( width );
316 status->width = width;
322 /* If a width is given, strtol() will return its value. If not given,
323 strtol() will return zero. In both cases, endptr will point to the
324 rest of the conversion specifier - just what we need.
326 status->width = (int)strtol( spec, (char**)&spec, 10 );
329 /* Optional precision */
335 /* Retrieve precision value from argument stack. A negative value
336 is as if no precision is given - as precision is initalized to
337 EOF (negative), there is no need for testing for negative here.
339 status->prec = va_arg( status->arg, int );
345 status->prec = (int)strtol( spec, &endptr, 10 );
346 if ( spec == endptr )
348 /* Decimal point but no number - equals zero */
353 /* Having a precision cancels out any zero flag. */
354 status->flags &= ~E_zero;
357 /* Optional length modifier
358 We step one character ahead in any case, and step back only if we find
359 there has been no length modifier (or step ahead another character if it
360 has been "hh" or "ll").
368 status->flags |= E_char;
374 status->flags |= E_short;
380 /* ll -> long long */
381 status->flags |= E_llong;
387 status->flags |= E_long;
391 /* j -> intmax_t, which might or might not be long long */
392 status->flags |= E_intmax;
395 /* z -> size_t, which might or might not be unsigned int */
396 status->flags |= E_size;
399 /* t -> ptrdiff_t, which might or might not be long */
400 status->flags |= E_ptrdiff;
403 /* L -> long double */
404 status->flags |= E_ldouble;
411 /* Conversion specifier */
421 status->flags |= E_unsigned;
425 status->flags |= E_unsigned;
429 status->flags |= ( E_lower | E_unsigned );
433 status->flags |= E_unsigned;
446 /* TODO: wide chars. */
449 c[0] = (char)va_arg( status->arg, int );
450 status->flags |= E_char;
451 stringformat( c, status );
455 /* TODO: wide chars. */
456 stringformat( va_arg( status->arg, char * ), status );
460 status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
464 int * val = va_arg( status->arg, int * );
469 /* No conversion specifier. Bad conversion. */
473 /* Do the actual output based on our findings */
474 if ( status->base != 0 )
476 /* Integer conversions */
477 /* TODO: Check for invalid flag combinations. */
478 if ( status->flags & E_unsigned )
481 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
484 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
487 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
490 value = (uintmax_t)va_arg( status->arg, unsigned int );
493 value = (uintmax_t)va_arg( status->arg, unsigned long );
496 value = (uintmax_t)va_arg( status->arg, unsigned long long );
499 value = (uintmax_t)va_arg( status->arg, size_t );
502 value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
505 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
509 if ( ( value / status->base ) != 0 )
511 /* Get value to "safe" levels re. uintmax_t. */
512 int2base( (intmax_t)(value / status->base), status );
516 intformat( (intmax_t)value, status );
518 int digit = value % status->base;
523 if ( status->flags & E_lower )
525 PUT( _PDCLIB_digits[ digit ] );
529 PUT( _PDCLIB_Xdigits[ digit ] );
534 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
537 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
540 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
543 int2base( (intmax_t)va_arg( status->arg, int ), status );
546 int2base( (intmax_t)va_arg( status->arg, long ), status );
549 int2base( (intmax_t)va_arg( status->arg, long long ), status );
552 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
555 int2base( va_arg( status->arg, intmax_t ), status );
558 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
562 if ( status->flags & E_minus )
564 while ( status->current < status->width )
570 if ( status->i >= status->n && status->n > 0 )
572 status->s[status->n - 1] = '\0';
581 #define _PDCLIB_FILEID "_PDCLIB/print.c"
582 #define _PDCLIB_STRINGIO
584 #include "_PDCLIB_test.h"
588 static int testprintf( char * buffer, const char * format, ... )
590 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
591 struct _PDCLIB_status_t status;
600 status.stream = NULL;
601 va_start( status.arg, format );
602 memset( buffer, '\0', 100 );
603 if ( *(_PDCLIB_print( format, &status )) != '\0' )
605 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
608 va_end( status.arg );
614 #define TEST_CONVERSION_ONLY
620 #include "printf_testcases.h"