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 /* Special case: zero value, zero precision -- no output (but padding) */
163 if ( status->current == 0 && value == 0 && status->prec == 0 )
165 intformat( value, status );
168 /* Registering the character being printed at the end of the function here
169 already so it will be taken into account when the deepestmost recursion
170 does the prefix / padding stuff.
173 if ( ( value / status->base ) != 0 )
175 /* More digits to be done - recurse deeper */
176 int2base( value / status->base, status );
180 /* We reached the last digit, the deepest point of our recursion, and
181 only now know how long the number to be printed actually is. Now we
182 have to do the sign, prefix, width, and precision padding stuff
183 before printing the numbers while we resurface from the recursion.
185 intformat( value, status );
187 /* Recursion tail - print the current digit. */
189 int digit = value % status->base;
194 if ( status->flags & E_lower )
196 /* Lowercase letters. Same array used for strto...(). */
197 PUT( _PDCLIB_digits[ digit ] );
201 /* Uppercase letters. Array only used here, only 0-F. */
202 PUT( _PDCLIB_Xdigits[ digit ] );
208 static void stringformat( const char * s, struct _PDCLIB_status_t * status )
210 if ( status->flags & E_char )
216 if ( status->prec < 0 )
218 status->prec = strlen( s );
222 for ( int i = 0; i < status->prec; ++i )
232 if ( ! ( status->flags & E_minus ) && ( status->width > (_PDCLIB_size_t)status->prec ) )
234 while ( status->current < ( status->width - status->prec ) )
240 while ( status->prec > 0 )
246 if ( status->flags & E_minus )
248 while ( status->width > status->current )
257 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
259 const char * orig_spec = spec;
260 if ( *(++spec) == '%' )
262 /* %% -> print single '%' */
266 /* Initializing status structure */
273 /* First come 0..n flags */
279 /* left-aligned output */
280 status->flags |= E_minus;
284 /* positive numbers prefixed with '+' */
285 status->flags |= E_plus;
289 /* alternative format (leading 0x for hex, 0 for octal) */
290 status->flags |= E_alt;
294 /* positive numbers prefixed with ' ' */
295 status->flags |= E_space;
299 /* right-aligned padding done with '0' instead of ' ' */
300 status->flags |= E_zero;
304 /* not a flag, exit flag parsing */
305 status->flags |= E_done;
308 } while ( ! ( status->flags & E_done ) );
310 /* Optional field width */
313 /* Retrieve width value from argument stack */
314 int width = va_arg( status->arg, int );
317 status->flags |= E_minus;
318 status->width = abs( width );
322 status->width = width;
328 /* If a width is given, strtol() will return its value. If not given,
329 strtol() will return zero. In both cases, endptr will point to the
330 rest of the conversion specifier - just what we need.
332 status->width = (int)strtol( spec, (char**)&spec, 10 );
335 /* Optional precision */
341 /* Retrieve precision value from argument stack. A negative value
342 is as if no precision is given - as precision is initalized to
343 EOF (negative), there is no need for testing for negative here.
345 status->prec = va_arg( status->arg, int );
351 status->prec = (int)strtol( spec, &endptr, 10 );
352 if ( spec == endptr )
354 /* Decimal point but no number - equals zero */
359 /* Having a precision cancels out any zero flag. */
360 status->flags &= ~E_zero;
363 /* Optional length modifier
364 We step one character ahead in any case, and step back only if we find
365 there has been no length modifier (or step ahead another character if it
366 has been "hh" or "ll").
374 status->flags |= E_char;
380 status->flags |= E_short;
386 /* ll -> long long */
387 status->flags |= E_llong;
393 status->flags |= E_long;
397 /* j -> intmax_t, which might or might not be long long */
398 status->flags |= E_intmax;
401 /* z -> size_t, which might or might not be unsigned int */
402 status->flags |= E_size;
405 /* t -> ptrdiff_t, which might or might not be long */
406 status->flags |= E_ptrdiff;
409 /* L -> long double */
410 status->flags |= E_ldouble;
417 /* Conversion specifier */
427 status->flags |= E_unsigned;
431 status->flags |= E_unsigned;
435 status->flags |= ( E_lower | E_unsigned );
439 status->flags |= E_unsigned;
452 /* TODO: wide chars. */
455 c[0] = (char)va_arg( status->arg, int );
456 status->flags |= E_char;
457 stringformat( c, status );
461 /* TODO: wide chars. */
462 stringformat( va_arg( status->arg, char * ), status );
466 status->flags |= ( E_lower | E_unsigned | E_alt | E_pointer );
470 int * val = va_arg( status->arg, int * );
475 /* No conversion specifier. Bad conversion. */
479 /* Do the actual output based on our findings */
480 if ( status->base != 0 )
482 /* Integer conversions */
483 /* TODO: Check for invalid flag combinations. */
484 if ( status->flags & E_unsigned )
487 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size | E_pointer ) )
490 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
493 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
496 value = (uintmax_t)va_arg( status->arg, unsigned int );
499 value = (uintmax_t)va_arg( status->arg, unsigned long );
502 value = (uintmax_t)va_arg( status->arg, unsigned long long );
505 value = (uintmax_t)va_arg( status->arg, size_t );
508 value = (uintmax_t)(uintptr_t)va_arg( status->arg, void * );
511 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
514 /* Special case: zero value, zero precision: No output, just padding */
515 if ( value == 0 && status->prec == 0 )
517 int2base( 0, status );
521 /* To make the call to int2base (using intmax_t) safe for
522 uintmax_t values > INTMAX_MAX, we basically to the first
523 "recursion" level of int2base right here.
526 if ( ( value / status->base ) != 0 )
528 int2base( (intmax_t)(value / status->base), status );
532 intformat( (intmax_t)value, status );
534 int digit = value % status->base;
539 if ( status->flags & E_lower )
541 PUT( _PDCLIB_digits[ digit ] );
545 PUT( _PDCLIB_Xdigits[ digit ] );
551 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
554 int2base( (intmax_t)(char)va_arg( status->arg, int ), status );
557 int2base( (intmax_t)(short)va_arg( status->arg, int ), status );
560 int2base( (intmax_t)va_arg( status->arg, int ), status );
563 int2base( (intmax_t)va_arg( status->arg, long ), status );
566 int2base( (intmax_t)va_arg( status->arg, long long ), status );
569 int2base( (intmax_t)va_arg( status->arg, ptrdiff_t ), status );
572 int2base( va_arg( status->arg, intmax_t ), status );
575 puts( "UNSUPPORTED PRINTF FLAG COMBINATION" );
579 if ( status->flags & E_minus )
581 while ( status->current < status->width )
587 if ( status->i >= status->n && status->n > 0 )
589 status->s[status->n - 1] = '\0';
598 #define _PDCLIB_FILEID "_PDCLIB/print.c"
599 #define _PDCLIB_STRINGIO
601 #include "_PDCLIB_test.h"
605 static int testprintf( char * buffer, const char * format, ... )
607 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
608 struct _PDCLIB_status_t status;
617 status.stream = NULL;
618 va_start( status.arg, format );
619 memset( buffer, '\0', 100 );
620 if ( *(_PDCLIB_print( format, &status )) != '\0' )
622 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
625 va_end( status.arg );
631 #define TEST_CONVERSION_ONLY
637 #include "printf_testcases.h"