The Pedigree Project 0.1
vsprintf.c
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
21/*
22 * Wirzenius wrote this portably, Torvalds fucked it up :-)
23 */
24
25#include "pedigree/kernel/processor/types.h"
26#include "pedigree/kernel/utilities/assert.h"
27#include "pedigree/kernel/utilities/utility.h"
28
29#include <stdarg.h>
30
31#ifdef __clang__
32#pragma GCC diagnostic ignored "-Wgnu-statement-expression"
33#endif
34
35/* we use this so that we can do without the ctype library */
36#define is_digit(c) ((c) >= '0' && (c) <= '9')
37
38static int skip_atoi(const char** s) {
39 int i = 0;
40
41 while (is_digit(**s))
42 i = i * 10 + *((*s)++) - '0';
43 return i;
44}
45
46#define ZEROPAD 1 /* pad with zero */
47#define SIGN 2 /* unsigned/signed long */
48#define PLUS 4 /* show plus */
49#define SPACE 8 /* space if plus */
50#define LEFT 16 /* left justified */
51#define SPECIAL 32 /* 0x */
52#define SMALL 64 /* use 'abcdef' instead of 'ABCDEF' */
53
54static char* number(char* str, int64_t num, int base, int size, int precision, int type) {
55 char c, sign, tmp[36];
56 const char* digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
57 uint64_t magnitude = (uint64_t)num;
58 int i;
59
60 if (type & SMALL)
61 digits = "0123456789abcdefghijklmnopqrstuvwxyz";
62 if (type & LEFT)
63 type &= ~ZEROPAD;
64 if (base < 2 || base > 36)
65 return 0;
66 c = (type & ZEROPAD) ? '0' : ' ';
67 if (type & SIGN && num < 0) {
68 sign = '-';
69 // Unsigned subtraction also handles the magnitude of INT64_MIN.
70 magnitude = (uint64_t)0 - magnitude;
71 } else
72 sign = (type & PLUS) ? '+' : ((type & SPACE) ? ' ' : 0);
73 if (sign)
74 size--;
75 if (type & SPECIAL) {
76 if (base == 16)
77 size -= 2;
78 else if (base == 8)
79 size--;
80 }
81 i = 0;
82 if (magnitude == 0) {
83 tmp[i++] = '0';
84 } else {
85 while (magnitude != 0) {
86 int d = (int)(magnitude % (uint64_t)base);
87 magnitude /= (uint64_t)base;
88 tmp[i++] = digits[d];
89 assert(i < 36);
90 }
91 }
92 if (i > precision)
93 precision = i;
94 size -= precision;
95 if (!(type & (ZEROPAD + LEFT)))
96 while (size-- > 0)
97 *str++ = ' ';
98 if (sign)
99 *str++ = sign;
100 if (type & SPECIAL) {
101 if (base == 8)
102 *str++ = '0';
103 else if (base == 16) {
104 *str++ = '0';
105 *str++ = digits[33];
106 }
107 }
108 if (!(type & LEFT))
109 while (size-- > 0)
110 *str++ = c;
111 while (i < precision--)
112 *str++ = '0';
113 while (i-- > 0)
114 *str++ = tmp[i];
115 while (size-- > 0)
116 *str++ = ' ';
117 return str;
118}
119
120int VStringFormat(char* buf, const char* fmt, va_list args) {
121 int len;
122 int i;
123 char* str;
124 char* s;
125 int* ip;
126
127 int flags; /* flags to number() */
128
129 int field_width; /* width of output field */
130 int precision; /* min. # of digits for integers; max
131 number of chars for from string */
132 int qualifier; /* 'h', 'l', or 'L' for integer fields */
133
134 for (str = buf; *fmt; ++fmt) {
135 if (*fmt != '%') {
136 *str++ = *fmt;
137 continue;
138 }
139
140 /* process flags */
141 flags = 0;
142 repeat:
143 ++fmt; /* this also skips first '%' */
144 switch (*fmt) {
145 case '-':
146 flags |= LEFT;
147 goto repeat;
148 case '+':
149 flags |= PLUS;
150 goto repeat;
151 case ' ':
152 flags |= SPACE;
153 goto repeat;
154 case '#':
155 flags |= SPECIAL;
156 goto repeat;
157 case '0':
158 flags |= ZEROPAD;
159 goto repeat;
160 }
161
162 /* get field width */
163 field_width = -1;
164 if (is_digit(*fmt))
165 field_width = skip_atoi(&fmt);
166 else if (*fmt == '*') {
167 /* it's the next argument */
168 field_width = va_arg(args, int);
169 if (field_width < 0) {
170 field_width = -field_width;
171 flags |= LEFT;
172 }
173 }
174
175 /* get the precision */
176 precision = -1;
177 if (*fmt == '.') {
178 ++fmt;
179 if (is_digit(*fmt))
180 precision = skip_atoi(&fmt);
181 else if (*fmt == '*') {
182 /* it's the next argument */
183 precision = va_arg(args, int);
184 }
185 if (precision < 0)
186 precision = 0;
187 }
188
189 /* get the conversion qualifier */
190 qualifier = -1;
191 if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
192 qualifier = *fmt;
193 ++fmt;
194 }
195
196 switch (*fmt) {
197 case 'c':
198 if (!(flags & LEFT))
199 while (--field_width > 0)
200 *str++ = ' ';
201 *str++ = (unsigned char)va_arg(args, int);
202 while (--field_width > 0)
203 *str++ = ' ';
204 break;
205
206 case 's':
207 s = va_arg(args, char*);
208 len = StringLength(s);
209 if (precision < 0)
210 precision = len;
211 else if (len > precision)
212 len = precision;
213
214 if (!(flags & LEFT))
215 while (len < field_width--)
216 *str++ = ' ';
217 for (i = 0; i < len; ++i)
218 *str++ = *s++;
219 while (len < field_width--)
220 *str++ = ' ';
221 break;
222
223 case 'o':
224 str = number(str, va_arg(args, unsigned long), 8, field_width, precision, flags);
225 break;
226
227 case 'p':
228 if (field_width == -1) {
229 field_width = sizeof(void*) * 2;
230 flags |= ZEROPAD;
231 }
232 str = number(str, (uintptr_t)va_arg(args, void*), 16, field_width, precision, flags);
233 break;
234
235 case 'x':
236 flags |= SMALL;
238 case 'X':
239 str = number(str, va_arg(args, unsigned long), 16, field_width, precision, flags);
240 break;
241
242 case 'd':
243 case 'i':
244 flags |= SIGN;
246 case 'u':
247 str = number(str, va_arg(args, unsigned long), 10, field_width, precision, flags);
248 break;
249
250 case 'n':
251 ip = va_arg(args, int*);
252 *ip = (str - buf);
253 break;
254
255 default:
256 if (*fmt != '%')
257 *str++ = '%';
258 if (*fmt)
259 *str++ = *fmt;
260 else
261 --fmt;
262 break;
263 }
264 }
265 *str = '\0';
266 return str - buf;
267}