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.
19 /* Using an integer's bits as flags for both the conversion flags and length
22 /* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the
23 width flags) into a combined field.
35 #define E_intmax 1<<10
37 #define E_ptrdiff 1<<12
38 #define E_intptr 1<<13
39 #define E_ldouble 1<<14
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; \
62 /* Maximum number of output characters =
63 * number of bits in (u)intmax_t / number of bits per character in smallest
64 * base. Smallest base is octal, 3 bits/char.
66 * Additionally require 2 extra characters for prefixes
68 static const size_t maxIntLen = sizeof(intmax_t) * CHAR_BIT / 3 + 1;
70 static void int2base( uintmax_t value, struct _PDCLIB_status_t * status )
73 if ( ! ( status->flags & E_unsigned ) )
75 intmax_t signval = (intmax_t) value;
76 bool negative = signval < 0;
77 value = signval < 0 ? -signval : signval;
83 else if ( status->flags & E_plus )
87 else if (status->flags & E_space )
93 // The user could theoretically ask for a silly buffer length here.
94 // Perhaps after a certain size we should malloc? Or do we refuse to protect
95 // them from their own stupidity?
96 size_t bufLen = (status->width > maxIntLen ? status->width : maxIntLen) + 2;
98 char * outend = outbuf + bufLen;
101 // Build up our output string - backwards
103 const char * digits = (status->flags & E_lower) ?
104 _PDCLIB_digits : _PDCLIB_Xdigits;
105 uintmax_t remaining = value;
107 uintmax_t digit = remaining % status->base;
108 remaining /= status->base;
110 outend[-++written] = digits[digit];
111 } while(remaining != 0);
114 // Pad field out to the precision specification
115 while( written < status->prec ) outend[-++written] = '0';
117 // If a field width specified, and zero padding was requested, then pad to
119 unsigned padding = 0;
120 if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
122 while( written < status->width )
124 outend[-++written] = '0';
132 if ( padding == 0 ) written++;
133 outend[-written] = sign;
135 else if ( status->flags & E_alt )
137 switch ( status->base )
140 if ( outend[-written] != '0' ) outend[-++written] = '0';
144 if ( value == 0 ) break;
146 written += padding < 2 ? 2 - padding : 0;
147 outend[-written ] = '0';
148 outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';
155 // Space padding to field width
156 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
158 while( written < status->width ) outend[-++written] = ' ';
162 status->current = written;
164 PUT( outend[-written--] );
167 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
169 const char * orig_spec = spec;
170 if ( *(++spec) == '%' )
172 /* %% -> print single '%' */
176 /* Initializing status structure */
183 /* First come 0..n flags */
189 /* left-aligned output */
190 status->flags |= E_minus;
194 /* positive numbers prefixed with '+' */
195 status->flags |= E_plus;
199 /* alternative format (leading 0x for hex, 0 for octal) */
200 status->flags |= E_alt;
204 /* positive numbers prefixed with ' ' */
205 status->flags |= E_space;
209 /* right-aligned padding done with '0' instead of ' ' */
210 status->flags |= E_zero;
214 /* not a flag, exit flag parsing */
215 status->flags |= E_done;
218 } while ( ! ( status->flags & E_done ) );
220 /* Optional field width */
223 /* Retrieve width value from argument stack */
224 int width = va_arg( status->arg, int );
227 status->flags |= E_minus;
228 status->width = abs( width );
232 status->width = width;
238 /* If a width is given, strtol() will return its value. If not given,
239 strtol() will return zero. In both cases, endptr will point to the
240 rest of the conversion specifier - just what we need.
242 status->width = (int)strtol( spec, (char**)&spec, 10 );
245 /* Optional precision */
251 /* Retrieve precision value from argument stack. A negative value
252 is as if no precision is given - as precision is initalized to
253 EOF (negative), there is no need for testing for negative here.
255 status->prec = va_arg( status->arg, int );
260 status->prec = (int)strtol( spec, &endptr, 10 );
261 if ( spec == endptr )
263 /* Decimal point but no number - bad conversion specifier. */
268 /* Having a precision cancels out any zero flag. */
269 status->flags ^= E_zero;
272 /* Optional length modifier
273 We step one character ahead in any case, and step back only if we find
274 there has been no length modifier (or step ahead another character if it
275 has been "hh" or "ll").
283 status->flags |= E_char;
289 status->flags |= E_short;
295 /* ll -> long long */
296 status->flags |= E_llong;
302 status->flags |= E_long;
306 /* j -> intmax_t, which might or might not be long long */
307 status->flags |= E_intmax;
310 /* z -> size_t, which might or might not be unsigned int */
311 status->flags |= E_size;
314 /* t -> ptrdiff_t, which might or might not be long */
315 status->flags |= E_ptrdiff;
318 /* L -> long double */
319 status->flags |= E_ldouble;
326 /* Conversion specifier */
336 status->flags |= E_unsigned;
340 status->flags |= E_unsigned;
344 status->flags |= ( E_lower | E_unsigned );
348 status->flags |= E_unsigned;
361 /* TODO: Flags, wide chars. */
362 PUT( va_arg( status->arg, int ) );
365 /* TODO: Flags, wide chars. */
367 char * s = va_arg( status->arg, char * );
375 /* TODO: E_long -> E_intptr */
377 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
381 int * val = va_arg( status->arg, int * );
386 /* No conversion specifier. Bad conversion. */
390 /* Do the actual output based on our findings */
391 if ( status->base != 0 )
393 /* Integer conversions */
394 /* TODO: Check for invalid flag combinations. */
395 if ( status->flags & E_unsigned )
398 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
401 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
404 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
407 value = (uintmax_t)va_arg( status->arg, unsigned int );
410 value = (uintmax_t)va_arg( status->arg, unsigned long );
413 value = (uintmax_t)va_arg( status->arg, unsigned long long );
416 value = (uintmax_t)va_arg( status->arg, size_t );
419 int2base( value, status );
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->current < status->width )
456 if ( status->i >= status->n && status->n > 0 )
458 status->s[status->n - 1] = '\0';
467 #define _PDCLIB_FILEID "_PDCLIB/print.c"
468 #define _PDCLIB_STRINGIO
470 #include <_PDCLIB_test.h>
473 static int testprintf( char * buffer, const char * format, ... )
475 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
476 struct _PDCLIB_status_t status;
485 status.stream = NULL;
486 va_start( status.arg, format );
487 memset( buffer, '\0', 100 );
488 if ( *(_PDCLIB_print( format, &status )) != '\0' )
490 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
493 va_end( status.arg );
498 #define TEST_CONVERSION_ONLY
504 #include "printf_testcases.h"