a8f7af61a5f038c57fbffebfd169a74e23633ce8
[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  * A printf context
45  *
46  * Contexts are used in order to be able to share code between
47  * vprintf() and vsnprintf(), without requiring the allocation of a
48  * buffer for vprintf().
49  */
50 struct printf_context {
51         /**
52          * Character handler
53          *
54          * @v ctx       Context
55          * @v c         Character
56          *
57          * This method is called for each character written to the
58          * formatted string.  It must increment @len.
59          */
60         void ( * handler ) ( struct printf_context *ctx, unsigned int c );
61         /** Length of formatted string */
62         size_t len;
63         /** Buffer for formatted string (used by printf_sputc()) */
64         char *buf;
65         /** Buffer length (used by printf_sputc()) */
66         size_t max_len;
67 };
68
69 /**
70  * Use lower-case for hexadecimal digits
71  *
72  * Note that this value is set to 0x20 since that makes for very
73  * efficient calculations.  (Bitwise-ORing with @c LCASE converts to a
74  * lower-case character, for example.)
75  */
76 #define LCASE 0x20
77
78 /**
79  * Use "alternate form"
80  *
81  * For hexadecimal numbers, this means to add a "0x" or "0X" prefix to
82  * the number.
83  */
84 #define ALT_FORM 0x02
85
86 /**
87  * Format a hexadecimal number
88  *
89  * @v end               End of buffer to contain number
90  * @v num               Number to format
91  * @v width             Minimum field width
92  * @ret ptr             End of buffer
93  *
94  * Fills a buffer in reverse order with a formatted hexadecimal
95  * number.  The number will be zero-padded to the specified width.
96  * Lower-case and "alternate form" (i.e. "0x" prefix) flags may be
97  * set.
98  *
99  * There must be enough space in the buffer to contain the largest
100  * number that this function can format.
101  */
102 static char * format_hex ( char *end, unsigned long long num, int width,
103                            int flags ) {
104         char *ptr = end;
105         int case_mod;
106
107         /* Generate the number */
108         case_mod = flags & LCASE;
109         do {
110                 *(--ptr) = "0123456789ABCDEF"[ num & 0xf ] | case_mod;
111                 num >>= 4;
112         } while ( num );
113
114         /* Zero-pad to width */
115         while ( ( end - ptr ) < width )
116                 *(--ptr) = '0';
117
118         /* Add "0x" or "0X" if alternate form specified */
119         if ( flags & ALT_FORM ) {
120                 *(--ptr) = 'X' | case_mod;
121                 *(--ptr) = '0';
122         }
123
124         return ptr;
125 }
126
127 /**
128  * Format a decimal number
129  *
130  * @v end               End of buffer to contain number
131  * @v num               Number to format
132  * @v width             Minimum field width
133  * @ret ptr             End of buffer
134  *
135  * Fills a buffer in reverse order with a formatted decimal number.
136  * The number will be space-padded to the specified width.
137  *
138  * There must be enough space in the buffer to contain the largest
139  * number that this function can format.
140  */
141 static char * format_decimal ( char *end, signed long num, int width ) {
142         char *ptr = end;
143         int negative = 0;
144
145         /* Generate the number */
146         if ( num < 0 ) {
147                 negative = 1;
148                 num = -num;
149         }
150         do {
151                 *(--ptr) = '0' + ( num % 10 );
152                 num /= 10;
153         } while ( num );
154
155         /* Add "-" if necessary */
156         if ( negative )
157                 *(--ptr) = '-';
158
159         /* Space-pad to width */
160         while ( ( end - ptr ) < width )
161                 *(--ptr) = ' ';
162
163         return ptr;
164 }
165
166 /**
167  * Write a formatted string to a printf context
168  *
169  * @v ctx               Context
170  * @v fmt               Format string
171  * @v args              Arguments corresponding to the format string
172  * @ret len             Length of formatted string
173  */
174 size_t vcprintf ( struct printf_context *ctx, const char *fmt, va_list args ) {
175         int flags;
176         int width;
177         uint8_t *length;
178         char *ptr;
179         char tmp_buf[32]; /* 32 is enough for all numerical formats.
180                            * Insane width fields could overflow this buffer. */
181
182         /* Initialise context */
183         ctx->len = 0;
184
185         for ( ; *fmt ; fmt++ ) {
186                 /* Pass through ordinary characters */
187                 if ( *fmt != '%' ) {
188                         ctx->handler ( ctx, *fmt );
189                         continue;
190                 }
191                 fmt++;
192                 /* Process flag characters */
193                 flags = 0;
194                 for ( ; ; fmt++ ) {
195                         if ( *fmt == '#' ) {
196                                 flags |= ALT_FORM;
197                         } else if ( *fmt == '0' ) {
198                                 /* We always 0-pad hex and space-pad decimal */
199                         } else {
200                                 /* End of flag characters */
201                                 break;
202                         }
203                 }
204                 /* Process field width */
205                 width = 0;
206                 for ( ; ; fmt++ ) {
207                         if ( ( ( unsigned ) ( *fmt - '0' ) ) < 10 ) {
208                                 width = ( width * 10 ) + ( *fmt - '0' );
209                         } else {
210                                 break;
211                         }
212                 }
213                 /* We don't do floating point */
214                 /* Process length modifier */
215                 length = &type_sizes[INT_LEN];
216                 for ( ; ; fmt++ ) {
217                         if ( *fmt == 'h' ) {
218                                 length--;
219                         } else if ( *fmt == 'l' ) {
220                                 length++;
221                         } else if ( *fmt == 'z' ) {
222                                 length = &type_sizes[SIZE_T_LEN];
223                         } else {
224                                 break;
225                         }
226                 }
227                 /* Process conversion specifier */
228                 ptr = tmp_buf + sizeof ( tmp_buf ) - 1;
229                 *ptr = '\0';
230                 if ( *fmt == 'c' ) {
231                         ctx->handler ( ctx, va_arg ( args, unsigned int ) );
232                 } else if ( *fmt == 's' ) {
233                         ptr = va_arg ( args, char * );
234                 } else if ( *fmt == 'p' ) {
235                         intptr_t ptrval;
236
237                         ptrval = ( intptr_t ) va_arg ( args, void * );
238                         ptr = format_hex ( ptr, ptrval, width, 
239                                            ( ALT_FORM | LCASE ) );
240                 } else if ( ( *fmt & ~0x20 ) == 'X' ) {
241                         unsigned long long hex;
242
243                         flags |= ( *fmt & 0x20 ); /* LCASE */
244                         if ( *length >= sizeof ( unsigned long long ) ) {
245                                 hex = va_arg ( args, unsigned long long );
246                         } else if ( *length >= sizeof ( unsigned long ) ) {
247                                 hex = va_arg ( args, unsigned long );
248                         } else {
249                                 hex = va_arg ( args, unsigned int );
250                         }
251                         ptr = format_hex ( ptr, hex, width, flags );
252                 } else if ( *fmt == 'd' ) {
253                         signed long decimal;
254
255                         if ( *length >= sizeof ( signed long ) ) {
256                                 decimal = va_arg ( args, signed long );
257                         } else {
258                                 decimal = va_arg ( args, signed int );
259                         }
260                         ptr = format_decimal ( ptr, decimal, width );
261                 } else {
262                         *(--ptr) = *fmt;
263                 }
264                 /* Write out conversion result */
265                 for ( ; *ptr ; ptr++ ) {
266                         ctx->handler ( ctx, *ptr );
267                 }
268         }
269
270         return ctx->len;
271 }
272
273 /**
274  * Write character to buffer
275  *
276  * @v ctx               Context
277  * @v c                 Character
278  */
279 static void printf_sputc ( struct printf_context *ctx, unsigned int c ) {
280         if ( ++ctx->len < ctx->max_len )
281                 ctx->buf[ctx->len-1] = 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 printf_context ctx;
299         size_t len;
300         size_t end;
301
302         /* Hand off to vcprintf */
303         ctx.handler = printf_sputc;
304         ctx.buf = buf;
305         ctx.max_len = size;
306         len = vcprintf ( &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, unsigned int c ) {
345         ++ctx->len;
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 }