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';
143 written += padding < 2 ? 2 - padding : 0;
144 outend[-written ] = '0';
145 outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';
152 // Space padding to field width
153 if ( ! ( status->flags & ( E_minus | E_zero ) ) )
155 while( written < status->width ) outend[-++written] = ' ';
159 status->current = written;
161 PUT( outend[-written--] );
164 const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )
166 const char * orig_spec = spec;
167 if ( *(++spec) == '%' )
169 /* %% -> print single '%' */
173 /* Initializing status structure */
180 /* First come 0..n flags */
186 /* left-aligned output */
187 status->flags |= E_minus;
191 /* positive numbers prefixed with '+' */
192 status->flags |= E_plus;
196 /* alternative format (leading 0x for hex, 0 for octal) */
197 status->flags |= E_alt;
201 /* positive numbers prefixed with ' ' */
202 status->flags |= E_space;
206 /* right-aligned padding done with '0' instead of ' ' */
207 status->flags |= E_zero;
211 /* not a flag, exit flag parsing */
212 status->flags |= E_done;
215 } while ( ! ( status->flags & E_done ) );
217 /* Optional field width */
220 /* Retrieve width value from argument stack */
221 int width = va_arg( status->arg, int );
224 status->flags |= E_minus;
225 status->width = abs( width );
229 status->width = width;
235 /* If a width is given, strtol() will return its value. If not given,
236 strtol() will return zero. In both cases, endptr will point to the
237 rest of the conversion specifier - just what we need.
239 status->width = (int)strtol( spec, (char**)&spec, 10 );
242 /* Optional precision */
248 /* Retrieve precision value from argument stack. A negative value
249 is as if no precision is given - as precision is initalized to
250 EOF (negative), there is no need for testing for negative here.
252 status->prec = va_arg( status->arg, int );
257 status->prec = (int)strtol( spec, &endptr, 10 );
258 if ( spec == endptr )
260 /* Decimal point but no number - bad conversion specifier. */
265 /* Having a precision cancels out any zero flag. */
266 status->flags ^= E_zero;
269 /* Optional length modifier
270 We step one character ahead in any case, and step back only if we find
271 there has been no length modifier (or step ahead another character if it
272 has been "hh" or "ll").
280 status->flags |= E_char;
286 status->flags |= E_short;
292 /* ll -> long long */
293 status->flags |= E_llong;
299 status->flags |= E_long;
303 /* j -> intmax_t, which might or might not be long long */
304 status->flags |= E_intmax;
307 /* z -> size_t, which might or might not be unsigned int */
308 status->flags |= E_size;
311 /* t -> ptrdiff_t, which might or might not be long */
312 status->flags |= E_ptrdiff;
315 /* L -> long double */
316 status->flags |= E_ldouble;
323 /* Conversion specifier */
333 status->flags |= E_unsigned;
337 status->flags |= E_unsigned;
341 status->flags |= ( E_lower | E_unsigned );
345 status->flags |= E_unsigned;
358 /* TODO: Flags, wide chars. */
359 PUT( va_arg( status->arg, int ) );
362 /* TODO: Flags, wide chars. */
364 char * s = va_arg( status->arg, char * );
372 /* TODO: E_long -> E_intptr */
374 status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
378 int * val = va_arg( status->arg, int * );
383 /* No conversion specifier. Bad conversion. */
387 /* Do the actual output based on our findings */
388 if ( status->base != 0 )
390 /* Integer conversions */
391 /* TODO: Check for invalid flag combinations. */
392 if ( status->flags & E_unsigned )
395 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
398 value = (uintmax_t)(unsigned char)va_arg( status->arg, int );
401 value = (uintmax_t)(unsigned short)va_arg( status->arg, int );
404 value = (uintmax_t)va_arg( status->arg, unsigned int );
407 value = (uintmax_t)va_arg( status->arg, unsigned long );
410 value = (uintmax_t)va_arg( status->arg, unsigned long long );
413 value = (uintmax_t)va_arg( status->arg, size_t );
416 int2base( value, status );
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->current < status->width )
453 if ( status->i >= status->n && status->n > 0 )
455 status->s[status->n - 1] = '\0';
464 #define _PDCLIB_FILEID "_PDCLIB/print.c"
465 #define _PDCLIB_STRINGIO
467 #include <_PDCLIB_test.h>
470 static int testprintf( char * buffer, const char * format, ... )
472 /* Members: base, flags, n, i, current, s, width, prec, stream, arg */
473 struct _PDCLIB_status_t status;
482 status.stream = NULL;
483 va_start( status.arg, format );
484 memset( buffer, '\0', 100 );
485 if ( *(_PDCLIB_print( format, &status )) != '\0' )
487 printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );
490 va_end( status.arg );
495 #define TEST_CONVERSION_ONLY
501 #include "printf_testcases.h"