Fix bug in %hh handling.
[polintos/scott/priv.git] / lib / c / freestanding / sprintf.cc
1 // sprintf() and related functions.
2 //
3 // This software is copyright (c) 2007 Scott Wood <scott@buserror.net>.
4 // 
5 // This software is provided 'as-is', without any express or implied warranty.
6 // In no event will the authors or contributors be held liable for any damages
7 // arising from the use of this software.
8 // 
9 // Permission is hereby granted to everyone, free of charge, to use, copy,
10 // modify, prepare derivative works of, publish, distribute, perform,
11 // sublicense, and/or sell copies of the Software, provided that the above
12 // copyright notice and disclaimer of warranty be included in all copies or
13 // substantial portions of this software.
14
15 #include <stdint.h>
16 #include <string.h>
17 #include <stdarg.h>
18 #include <limits.h>
19
20 enum {
21         alt_form =        0x0001,
22         zero_pad =        0x0002,
23         neg_field =       0x0004,
24         leave_blank =     0x0008,
25         always_sign =     0x0010,
26         group_thousands = 0x0020, // FIXME -- unimplemented
27         long_arg =        0x0040,
28         long_long_arg =   0x0080,
29         short_arg =       0x0100,
30         short_short_arg = 0x0200,
31         intmax_arg =      0x0400,
32         ptrdiff_arg =     0x0800,
33         size_t_arg =      0x1000,
34         capital_hex =     0x2000,
35         num_signed =      0x4000,
36         has_precision =   0x8000,
37 };
38
39 static void printf_string(char *buf, size_t &opos, size_t limit,
40                           const char *src, size_t len)
41 {
42         if (opos < limit) {
43                 size_t olen = opos + len <= limit ? len : limit - opos;
44                 memcpy(buf + opos, src, olen);
45         }
46
47         opos += len;
48 }
49
50 static void printf_fill(char *buf, size_t &opos, size_t limit,
51                         char ch, int len)
52 {
53         if (opos < limit) {
54                 size_t olen = opos + len <= limit ? len : limit - opos;
55                 memset(buf + opos, ch, olen);
56         }
57         
58         opos += len;
59 }
60
61 static void printf_num(char *obuf, size_t &opos, size_t limit,
62                        int64_t value, long radix, int fieldwidth,
63                        int precision, int flags)
64 {
65         char buf[65];
66         int pos = 64;
67         int letter = (flags & capital_hex) ? 'A' - 10 : 'a' - 10;
68         uint64_t uval;
69
70         if (flags & num_signed)
71                 uval = value < 0 ? -value : value;
72         else
73                 uval = value;
74         
75         // An explicit precision of 0 suppresses all output if the value
76         // is zero.  Otherwise, the output size is not limited by precision
77         // or field width.
78         
79         if (uval != 0 || !(flags & has_precision) || precision != 0) do {
80                 int ch = uval % radix;
81                 
82                 if (ch < 10)
83                         buf[pos] = ch + '0';
84                 else
85                         buf[pos] = ch + letter;
86                 
87                 uval /= radix;
88                 pos--;
89         } while (uval);
90         
91         int len = 64 - pos;
92
93         // length which counts against fieldwidth but not precision
94         int extralen = 0; 
95         
96         if (flags & num_signed) {
97                 if (value < 0) {
98                         printf_fill(obuf, opos, limit, '-', 1);
99                         extralen += 1;
100                 } else if (flags & always_sign) {
101                         printf_fill(obuf, opos, limit, '+', 1);
102                         extralen += 1;
103                 } else if (flags & leave_blank) {
104                         printf_fill(obuf, opos, limit, ' ', 1);
105                         extralen += 1;
106                 }
107         }
108         
109         if ((flags & alt_form) && value != 0) {
110                 if (radix == 8 && (!(flags & has_precision) || precision <= len)) {
111                         flags |= has_precision;
112                         precision = len + 1;
113                 }
114                 
115                 if (radix == 16) {
116                         printf_string(obuf, opos, limit, "0x", 2);
117                         extralen += 2;
118                 }
119         }
120         
121         if ((flags & has_precision) && len < precision) {
122                 precision -= len;
123                 len += precision;
124         } else {
125                 precision = 0;
126         }
127         
128         len += extralen;
129         
130         if (!(flags & neg_field) && len < fieldwidth) {
131                 char padchar = (flags & zero_pad) ? '0' : ' ';
132                 printf_fill(obuf, opos, limit, padchar, fieldwidth - len);
133                 len = fieldwidth;
134         }
135
136         if (precision != 0) {
137                 printf_fill(obuf, opos, limit, '0', precision);
138                 len += precision;
139         }
140         
141         printf_string(obuf, opos, limit, buf + pos + 1, 64 - pos);
142         
143         if ((flags & neg_field) && len < fieldwidth)
144                 printf_fill(obuf, opos, limit, ' ', fieldwidth - len);
145 }
146
147 extern "C" size_t vsnprintf(char *buf, size_t size,
148                             const char *str, va_list args)
149 {
150         size_t opos = 0; // position in the output string
151         unsigned int flags = 0;
152         int radix = 10;
153         int state = 0;
154         int fieldwidth = 0;
155         int precision = 0;
156
157         for (size_t pos = 0; str[pos]; pos++) switch (state) {
158                 case 0:
159                         if (str[pos] == '%') {
160                                 flags = 0;
161                                 radix = 10;
162                                 state = 1;
163                                 fieldwidth = 0;
164                                 precision = 0;
165                                 break;
166                         }
167                 
168                         if (opos < size - 1)
169                                 buf[opos] = str[pos];
170
171                         opos++;
172                         break;
173                 
174                 case 1: // A percent has been seen; read in format characters
175                         switch (str[pos]) {
176                                 case '#':
177                                         flags |= alt_form;
178                                         break;
179                                 
180                                 case '0':
181                                         if (!(flags & has_precision)) {
182                                                 flags |= zero_pad;
183                                                 break;
184                                         }
185                                         
186                                         // else fall through
187                                 
188                                 case '1' ... '9':
189                                         if (flags & has_precision)
190                                                 goto default_case;
191                                         
192                                         do {
193                                                 fieldwidth *= 10;
194                                                 fieldwidth += str[pos++] - '0';
195                                         } while (str[pos] >= '0' && str[pos] <= '9');
196                                         
197                                         pos--;
198                                         break;
199                                 
200                                 case '*':
201                                         if (fieldwidth || (flags & has_precision))
202                                                 goto default_case;
203                                         
204                                         fieldwidth = va_arg(args, int);
205                                         break;
206                                 
207                                 case '.':
208                                         flags |= has_precision;
209                                         
210                                         if (str[pos + 1] == '*') {
211                                                 pos++;
212                                                 precision = va_arg(args, int);
213                                         } else while (str[pos + 1] >= '0' && str[pos + 1] <= '9') {
214                                                 precision *= 10;
215                                                 precision += str[++pos] - '0';
216                                         }
217                                         
218                                         break;
219                                                 
220                                 case '-':
221                                         flags |= neg_field;
222                                         break;
223                                 
224                                 case ' ':
225                                         flags |= leave_blank;
226                                         break;
227                                 
228                                 case '+':
229                                         flags |= always_sign;
230                                         break;
231                                 
232                                 case '\'':
233                                         flags |= group_thousands;
234                                         break;
235
236                                 case 'l':
237                                         if (flags & long_arg)
238                                                 flags |= long_long_arg;
239                                         else
240                                                 flags |= long_arg;
241         
242                                         break;
243                                 
244                                 case 'h':
245                                         if (flags & short_arg)
246                                                 flags |= short_short_arg;
247                                         else
248                                                 flags |= short_arg;
249
250                                         break;
251                                 
252                                 case 'j':
253                                         flags |= intmax_arg;
254                                         break;
255                                 
256                                 case 't':
257                                         flags |= ptrdiff_arg;
258                                         break;
259                                 
260                                 // Note that %z and other such "new" format characters are
261                                 // basically useless because some GCC coder actually went out
262                                 // of their way to make the compiler reject C99 format
263                                 // strings in C++ code, with no way of overriding it that I
264                                 // can find (the source code comments suggest the checking is
265                                 // only when using -pedantic, but I wasn't using -pedantic).
266                                 //
267                                 // Thus, we have the choice of either avoiding %z and friends
268                                 // (and possibly needing to insert hackish casts to silence
269                                 // the compiler's warnings if different architectures define
270                                 // types like size_t in different ways), or not using the
271                                 // format warnings at all.
272                                 //
273                                 // To mitigate this, 32-bit architectures should define
274                                 // pointer-sized special types as "long" rather than "int",
275                                 // so that %lx/%ld can always be used with them.  Fixed-size
276                                 // 32-bit types should be declared as "int" rather than
277                                 // "long" for the same reason.
278                                 
279                                 case 'z':
280                                         flags |= size_t_arg;
281                                         break;
282                                 
283                                 case 'd':
284                                 case 'i': {
285                                         int64_t arg;
286                                 
287                                         if ((flags & intmax_arg) || (flags & long_long_arg))
288                                                 arg = va_arg(args, long long);
289                                         else if (flags & size_t_arg)
290                                                 arg = va_arg(args, ssize_t);
291                                         else if (flags & ptrdiff_arg)
292                                                 arg = va_arg(args, ptrdiff_t);
293                                         else if (flags & long_arg)
294                                                 arg = va_arg(args, long);
295                                         else if (flags & short_short_arg)
296                                                 arg = (signed char)va_arg(args, int);
297                                         else if (flags & short_arg)
298                                                 arg = (short)va_arg(args, int);
299                                         else
300                                                 arg = va_arg(args, int);
301                                         
302                                         flags |= num_signed;
303                                         printf_num(buf, opos, size - 1, arg, 10,
304                                                    fieldwidth, precision, flags);
305                                         state = 0;
306                                         break;
307                                 }
308
309                                 case 'X':
310                                         flags |= capital_hex;
311                                         // fall-through
312
313                                 case 'x':
314                                         radix = 18;
315                                         // fall-through
316                                         
317                                 case 'o':
318                                         radix -= 2;
319                                         // fall-through
320                                 
321                                 case 'u': {
322                                         uint64_t arg;
323                                 
324                                         if ((flags & intmax_arg) || (flags & long_long_arg))
325                                                 arg = va_arg(args, unsigned long long);
326                                         else if (flags & size_t_arg)
327                                                 arg = va_arg(args, size_t);
328                                         else if (flags & ptrdiff_arg)
329                                                 arg = va_arg(args, intptr_t);
330                                         else if (flags & long_arg)
331                                                 arg = va_arg(args, unsigned long);
332                                         else if (flags & short_short_arg)
333                                                 arg = (unsigned char)va_arg(args, unsigned int);
334                                         else if (flags & short_arg)
335                                                 arg = (unsigned short)va_arg(args, unsigned int);
336                                         else if (flags & short_short_arg)
337                                                 arg = (signed char)va_arg(args, int);
338                                         else if (flags & short_arg)
339                                                 arg = (short)va_arg(args, int);
340                                         else
341                                                 arg = va_arg(args, unsigned int);
342                                         
343                                         printf_num(buf, opos, size - 1, arg, radix,
344                                                    fieldwidth, precision, flags);
345                                         state = 0;
346                                         break;
347                                 }
348                                 
349                                 case 'c':
350                                         if (opos < size - 1)
351                                                 buf[opos] = va_arg(args, int);
352         
353                                         opos++;
354                                         state = 0;
355                                         break;
356                                 
357                                 case 's': {
358                                         const char *arg = va_arg(args, const char *);
359                                         
360                                         if (!arg)
361                                                 arg = "(null)";
362                                         
363                                         size_t len = strlen(arg);
364                                         printf_string(buf, opos, size - 1, arg, len);
365                                         state = 0;
366                                         break;
367                                 }
368                                 
369                                 case 'p': {
370                                         const void *arg = va_arg(args, const void *);
371
372                                         printf_num(buf, opos, size - 1, (unsigned long)arg, 16,
373                                                    fieldwidth, precision, flags);
374                                         
375                                         state = 0;
376                                         break;
377                                 }
378                                 
379                                 case 'n': {
380                                         if ((flags & intmax_arg) || (flags & long_long_arg))
381                                                 *va_arg(args, unsigned long long *) = opos;
382                                         else if (flags & size_t_arg)
383                                                 *va_arg(args, ssize_t *) = opos;
384                                         else if (flags & ptrdiff_arg)
385                                                 *va_arg(args, ptrdiff_t *) = opos;
386                                         else if (flags & long_arg)
387                                                 *va_arg(args, long *) = opos;
388                                         else if (flags & short_short_arg)
389                                                 *va_arg(args, signed char *) = opos;
390                                         else if (flags & short_arg)
391                                                 *va_arg(args, short *) = opos;
392                                         else
393                                                 *va_arg(args, int *) = opos;
394                                                 
395                                         state = 0;
396                                         break;
397                                 }
398
399                                 default_case: // label for goto
400                                 default:
401                                         if (opos < size - 1)
402                                                 buf[opos] = str[pos];
403                                         
404                                         opos++;
405                                         state = 0;
406                                         break;
407                         }
408         }
409
410         if (opos < size)
411                 buf[opos] = 0;
412         else if (size > 0)
413                 buf[size - 1] = 0;
414         
415         return opos;
416 }
417
418 extern "C" size_t snprintf(char *buf, size_t size, const char *str, ...)
419 {
420         va_list args;
421         va_start(args, str);
422         int ret = vsnprintf(buf, size, str, args);
423         va_end(args);
424         return ret;
425 }
426
427 extern "C" size_t sprintf(char *buf, const char *str, ...)
428 {
429         va_list args;
430         va_start(args, str);
431         int ret = vsnprintf(buf, ULONG_MAX, str, args);
432         va_end(args);
433         return ret;
434 }