7626787d0e60c3de4bb04704cf6c661d67ac6835
[people/xl0/gpxe.git] / src / core / vsprintf.c
1 /*
2  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stddef.h>
20 #include <stdarg.h>
21 #include <console.h>
22 #include <errno.h>
23 #include <vsprintf.h>
24
25 /** @file */
26
27 #define CHAR_LEN        0       /**< "hh" length modifier */
28 #define SHORT_LEN       1       /**< "h" length modifier */
29 #define INT_LEN         2       /**< no length modifier */
30 #define LONG_LEN        3       /**< "l" length modifier */
31 #define LONGLONG_LEN    4       /**< "ll" length modifier */
32 #define SIZE_T_LEN      5       /**< "z" length modifier */
33
34 static uint8_t type_sizes[] = {
35         [CHAR_LEN]      = sizeof ( char ),
36         [SHORT_LEN]     = sizeof ( short ),
37         [INT_LEN]       = sizeof ( int ),
38         [LONG_LEN]      = sizeof ( long ),
39         [LONGLONG_LEN]  = sizeof ( long long ),
40         [SIZE_T_LEN]    = sizeof ( size_t ),
41 };
42
43 /**
44  * Use lower-case for hexadecimal digits
45  *
46  * Note that this value is set to 0x20 since that makes for very
47  * efficient calculations.  (Bitwise-ORing with @c LCASE converts to a
48  * lower-case character, for example.)
49  */
50 #define LCASE 0x20
51
52 /**
53  * Use "alternate form"
54  *
55  * For hexadecimal numbers, this means to add a "0x" or "0X" prefix to
56  * the number.
57  */
58 #define ALT_FORM 0x02
59
60 /**
61  * Format a hexadecimal number
62  *
63  * @v end               End of buffer to contain number
64  * @v num               Number to format
65  * @v width             Minimum field width
66  * @ret ptr             End of buffer
67  *
68  * Fills a buffer in reverse order with a formatted hexadecimal
69  * number.  The number will be zero-padded to the specified width.
70  * Lower-case and "alternate form" (i.e. "0x" prefix) flags may be
71  * set.
72  *
73  * There must be enough space in the buffer to contain the largest
74  * number that this function can format.
75  */
76 static char * format_hex ( char *end, unsigned long long num, int width,
77                            int flags ) {
78         char *ptr = end;
79         int case_mod;
80
81         /* Generate the number */
82         case_mod = flags & LCASE;
83         do {
84                 *(--ptr) = "0123456789ABCDEF"[ num & 0xf ] | case_mod;
85                 num >>= 4;
86         } while ( num );
87
88         /* Zero-pad to width */
89         while ( ( end - ptr ) < width )
90                 *(--ptr) = '0';
91
92         /* Add "0x" or "0X" if alternate form specified */
93         if ( flags & ALT_FORM ) {
94                 *(--ptr) = 'X' | case_mod;
95                 *(--ptr) = '0';
96         }
97
98         return ptr;
99 }
100
101 /**
102  * Format a decimal number
103  *
104  * @v end               End of buffer to contain number
105  * @v num               Number to format
106  * @v width             Minimum field width
107  * @ret ptr             End of buffer
108  *
109  * Fills a buffer in reverse order with a formatted decimal number.
110  * The number will be space-padded to the specified width.
111  *
112  * There must be enough space in the buffer to contain the largest
113  * number that this function can format.
114  */
115 static char * format_decimal ( char *end, signed long num, int width ) {
116         char *ptr = end;
117         int negative = 0;
118
119         /* Generate the number */
120         if ( num < 0 ) {
121                 negative = 1;
122                 num = -num;
123         }
124         do {
125                 *(--ptr) = '0' + ( num % 10 );
126                 num /= 10;
127         } while ( num );
128
129         /* Add "-" if necessary */
130         if ( negative )
131                 *(--ptr) = '-';
132
133         /* Space-pad to width */
134         while ( ( end - ptr ) < width )
135                 *(--ptr) = ' ';
136
137         return ptr;
138 }
139
140 /**
141  * Print character via a printf context
142  *
143  * @v ctx               Context
144  * @v c                 Character
145  *
146  * Call's the printf_context::handler() method and increments
147  * printf_context::len.
148  */
149 static inline void cputchar ( struct printf_context *ctx, unsigned int c ) {
150         ctx->handler ( ctx, c );
151         ++ctx->len;
152 }
153
154 /**
155  * Write a formatted string to a printf context
156  *
157  * @v ctx               Context
158  * @v fmt               Format string
159  * @v args              Arguments corresponding to the format string
160  * @ret len             Length of formatted string
161  */
162 size_t vcprintf ( struct printf_context *ctx, const char *fmt, va_list args ) {
163         int flags;
164         int width;
165         uint8_t *length;
166         char *ptr;
167         char tmp_buf[32]; /* 32 is enough for all numerical formats.
168                            * Insane width fields could overflow this buffer. */
169
170         /* Initialise context */
171         ctx->len = 0;
172
173         for ( ; *fmt ; fmt++ ) {
174                 /* Pass through ordinary characters */
175                 if ( *fmt != '%' ) {
176                         cputchar ( ctx, *fmt );
177                         continue;
178                 }
179                 fmt++;
180                 /* Process flag characters */
181                 flags = 0;
182                 for ( ; ; fmt++ ) {
183                         if ( *fmt == '#' ) {
184                                 flags |= ALT_FORM;
185                         } else if ( *fmt == '0' ) {
186                                 /* We always 0-pad hex and space-pad decimal */
187                         } else {
188                                 /* End of flag characters */
189                                 break;
190                         }
191                 }
192                 /* Process field width */
193                 width = 0;
194                 for ( ; ; fmt++ ) {
195                         if ( ( ( unsigned ) ( *fmt - '0' ) ) < 10 ) {
196                                 width = ( width * 10 ) + ( *fmt - '0' );
197                         } else {
198                                 break;
199                         }
200                 }
201                 /* We don't do floating point */
202                 /* Process length modifier */
203                 length = &type_sizes[INT_LEN];
204                 for ( ; ; fmt++ ) {
205                         if ( *fmt == 'h' ) {
206                                 length--;
207                         } else if ( *fmt == 'l' ) {
208                                 length++;
209                         } else if ( *fmt == 'z' ) {
210                                 length = &type_sizes[SIZE_T_LEN];
211                         } else {
212                                 break;
213                         }
214                 }
215                 /* Process conversion specifier */
216                 ptr = tmp_buf + sizeof ( tmp_buf ) - 1;
217                 *ptr = '\0';
218                 if ( *fmt == 'c' ) {
219                         cputchar ( ctx, va_arg ( args, unsigned int ) );
220                 } else if ( *fmt == 's' ) {
221                         ptr = va_arg ( args, char * );
222                 } else if ( *fmt == 'p' ) {
223                         intptr_t ptrval;
224
225                         ptrval = ( intptr_t ) va_arg ( args, void * );
226                         ptr = format_hex ( ptr, ptrval, width, 
227                                            ( ALT_FORM | LCASE ) );
228                 } else if ( ( *fmt & ~0x20 ) == 'X' ) {
229                         unsigned long long hex;
230
231                         flags |= ( *fmt & 0x20 ); /* LCASE */
232                         if ( *length >= sizeof ( unsigned long long ) ) {
233                                 hex = va_arg ( args, unsigned long long );
234                         } else if ( *length >= sizeof ( unsigned long ) ) {
235                                 hex = va_arg ( args, unsigned long );
236                         } else {
237                                 hex = va_arg ( args, unsigned int );
238                         }
239                         ptr = format_hex ( ptr, hex, width, flags );
240                 } else if ( ( *fmt == 'd' ) || ( *fmt == 'i' ) ){
241                         signed long decimal;
242
243                         if ( *length >= sizeof ( signed long ) ) {
244                                 decimal = va_arg ( args, signed long );
245                         } else {
246                                 decimal = va_arg ( args, signed int );
247                         }
248                         ptr = format_decimal ( ptr, decimal, width );
249                 } else {
250                         *(--ptr) = *fmt;
251                 }
252                 /* Write out conversion result */
253                 for ( ; *ptr ; ptr++ ) {
254                         cputchar ( ctx, *ptr );
255                 }
256         }
257
258         return ctx->len;
259 }
260
261 /** Context used by vsnprintf() and friends */
262 struct sputc_context {
263         struct printf_context ctx;
264         /** Buffer for formatted string (used by printf_sputc()) */
265         char *buf;
266         /** Buffer length (used by printf_sputc()) */
267         size_t max_len; 
268 };
269
270 /**
271  * Write character to buffer
272  *
273  * @v ctx               Context
274  * @v c                 Character
275  */
276 static void printf_sputc ( struct printf_context *ctx, unsigned int c ) {
277         struct sputc_context * sctx =
278                 container_of ( ctx, struct sputc_context, ctx );
279
280         if ( ctx->len <= sctx->max_len )
281                 sctx->buf[ctx->len] = c;
282 }
283
284 /**
285  * Write a formatted string to a buffer
286  *
287  * @v buf               Buffer into which to write the string
288  * @v size              Size of buffer
289  * @v fmt               Format string
290  * @v args              Arguments corresponding to the format string
291  * @ret len             Length of formatted string
292  *
293  * If the buffer is too small to contain the string, the returned
294  * length is the length that would have been written had enough space
295  * been available.
296  */
297 int vsnprintf ( char *buf, size_t size, const char *fmt, va_list args ) {
298         struct sputc_context sctx;
299         size_t len;
300         size_t end;
301
302         /* Hand off to vcprintf */
303         sctx.ctx.handler = printf_sputc;
304         sctx.buf = buf;
305         sctx.max_len = size;
306         len = vcprintf ( &sctx.ctx, fmt, args );
307
308         /* Add trailing NUL */
309         if ( size ) {
310                 end = size - 1;
311                 if ( len < end )
312                         end = len;
313                 buf[end] = '\0';
314         }
315
316         return len;
317 }
318
319 /**
320  * Write a formatted string to a buffer
321  *
322  * @v buf               Buffer into which to write the string
323  * @v size              Size of buffer
324  * @v fmt               Format string
325  * @v ...               Arguments corresponding to the format string
326  * @ret len             Length of formatted string
327  */
328 int snprintf ( char *buf, size_t size, const char *fmt, ... ) {
329         va_list args;
330         int i;
331
332         va_start ( args, fmt );
333         i = vsnprintf ( buf, size, fmt, args );
334         va_end ( args );
335         return i;
336 }
337
338 /**
339  * Write character to console
340  *
341  * @v ctx               Context
342  * @v c                 Character
343  */
344 static void printf_putchar ( struct printf_context *ctx __unused,
345                              unsigned int c ) {
346         putchar ( c );
347 }
348
349 /**
350  * Write a formatted string to the console
351  *
352  * @v fmt               Format string
353  * @v args              Arguments corresponding to the format string
354  * @ret len             Length of formatted string
355  */
356 int vprintf ( const char *fmt, va_list args ) {
357         struct printf_context ctx;
358
359         /* Hand off to vcprintf */
360         ctx.handler = printf_putchar;   
361         return vcprintf ( &ctx, fmt, args );    
362 }
363
364 /**
365  * Write a formatted string to the console.
366  *
367  * @v fmt               Format string
368  * @v ...               Arguments corresponding to the format string
369  * @ret len             Length of formatted string
370  */
371 int printf ( const char *fmt, ... ) {
372         va_list args;
373         int i;
374
375         va_start ( args, fmt );
376         i = vprintf ( fmt, args );
377         va_end ( args );
378         return i;
379 }