Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#pragma once
28
29#include <AK/Assertions.h>
30#include <AK/LogStream.h>
31#include <AK/StdLibExtras.h>
32#include <AK/Types.h>
33#include <stdarg.h>
34
35static constexpr const char* printf_hex_digits_lower = "0123456789abcdef";
36static constexpr const char* printf_hex_digits_upper = "0123456789ABCDEF";
37
38#ifdef __serenity__
39extern "C" size_t strlen(const char*);
40#else
41# include <string.h>
42#endif
43
44template<typename PutChFunc, typename T>
45[[gnu::always_inline]] inline int print_hex(PutChFunc putch, char*& bufptr, T number, bool upper_case, bool alternate_form, bool left_pad, bool zeroPad, u8 width)
46{
47 int ret = 0;
48
49 int digits = 0;
50 for (T n = number; n > 0; n >>= 4)
51 ++digits;
52 if (digits == 0)
53 digits = 1;
54
55 if (left_pad) {
56 int stop_at = width - digits;
57 if (alternate_form)
58 stop_at -= 2;
59
60 while (ret < stop_at) {
61 putch(bufptr, ' ');
62 ++ret;
63 }
64 }
65
66 if (alternate_form) {
67 putch(bufptr, '0');
68 putch(bufptr, 'x');
69 ret += 2;
70 width += 2;
71 }
72
73 if (zeroPad) {
74 while (ret < width - digits) {
75 putch(bufptr, '0');
76 ++ret;
77 }
78 }
79
80 if (number == 0) {
81 putch(bufptr, '0');
82 ++ret;
83 } else {
84 u8 shift_count = digits * 4;
85 while (shift_count) {
86 shift_count -= 4;
87 putch(bufptr,
88 upper_case
89 ? printf_hex_digits_upper[(number >> shift_count) & 0x0f]
90 : printf_hex_digits_lower[(number >> shift_count) & 0x0f]);
91 ++ret;
92 }
93 }
94
95 return ret;
96}
97
98template<typename PutChFunc>
99[[gnu::always_inline]] inline int print_number(PutChFunc putch, char*& bufptr, u32 number, bool leftPad, bool zeroPad, u32 fieldWidth)
100{
101 u32 divisor = 1000000000;
102 char ch;
103 char padding = 1;
104 char buf[16];
105 char* p = buf;
106
107 for (;;) {
108 ch = '0' + (number / divisor);
109 number %= divisor;
110 if (ch != '0')
111 padding = 0;
112 if (!padding || divisor == 1)
113 *(p++) = ch;
114 if (divisor == 1)
115 break;
116 divisor /= 10;
117 }
118
119 size_t numlen = p - buf;
120 if (!fieldWidth || fieldWidth < numlen)
121 fieldWidth = numlen;
122 if (!leftPad) {
123 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
124 putch(bufptr, zeroPad ? '0' : ' ');
125 }
126 }
127 for (unsigned i = 0; i < numlen; ++i) {
128 putch(bufptr, buf[i]);
129 }
130 if (leftPad) {
131 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
132 putch(bufptr, ' ');
133 }
134 }
135
136 return fieldWidth;
137}
138
139template<typename PutChFunc>
140[[gnu::always_inline]] inline int print_u64(PutChFunc putch, char*& bufptr, u64 number, bool leftPad, bool zeroPad, u32 fieldWidth)
141{
142 u64 divisor = 10000000000000000000LLU;
143 char ch;
144 char padding = 1;
145 char buf[16];
146 char* p = buf;
147
148 for (;;) {
149 ch = '0' + (number / divisor);
150 number %= divisor;
151 if (ch != '0')
152 padding = 0;
153 if (!padding || divisor == 1)
154 *(p++) = ch;
155 if (divisor == 1)
156 break;
157 divisor /= 10;
158 }
159
160 size_t numlen = p - buf;
161 if (!fieldWidth || fieldWidth < numlen)
162 fieldWidth = numlen;
163 if (!leftPad) {
164 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
165 putch(bufptr, zeroPad ? '0' : ' ');
166 }
167 }
168 for (unsigned i = 0; i < numlen; ++i) {
169 putch(bufptr, buf[i]);
170 }
171 if (leftPad) {
172 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
173 putch(bufptr, ' ');
174 }
175 }
176
177 return fieldWidth;
178}
179
180template<typename PutChFunc>
181[[gnu::always_inline]] inline int print_double(PutChFunc putch, char*& bufptr, double number, bool leftPad, bool zeroPad, u32 fieldWidth, u32 fraction_length = 6)
182{
183 int length = 0;
184
185 if (number < 0) {
186 putch(bufptr, '-');
187 length++;
188 number = 0 - number;
189 }
190
191 length = print_u64(putch, bufptr, (i64)number, leftPad, zeroPad, fieldWidth);
192 putch(bufptr, '.');
193 length++;
194 double fraction = number - (i64)number;
195
196 for (u32 i = 0; i < fraction_length; ++i)
197 fraction = fraction * 10;
198
199 return length + print_u64(putch, bufptr, (i64)fraction, false, true, fraction_length);
200}
201
202template<typename PutChFunc>
203[[gnu::always_inline]] inline int print_i64(PutChFunc putch, char*& bufptr, i64 number, bool leftPad, bool zeroPad, u32 fieldWidth)
204{
205 if (number < 0) {
206 putch(bufptr, '-');
207 return print_u64(putch, bufptr, 0 - number, leftPad, zeroPad, fieldWidth) + 1;
208 }
209 return print_u64(putch, bufptr, number, leftPad, zeroPad, fieldWidth);
210}
211
212template<typename PutChFunc>
213[[gnu::always_inline]] inline int print_octal_number(PutChFunc putch, char*& bufptr, u32 number, bool leftPad, bool zeroPad, u32 fieldWidth)
214{
215 u32 divisor = 134217728;
216 char ch;
217 char padding = 1;
218 char buf[32];
219 char* p = buf;
220
221 for (;;) {
222 ch = '0' + (number / divisor);
223 number %= divisor;
224 if (ch != '0')
225 padding = 0;
226 if (!padding || divisor == 1)
227 *(p++) = ch;
228 if (divisor == 1)
229 break;
230 divisor /= 8;
231 }
232
233 size_t numlen = p - buf;
234 if (!fieldWidth || fieldWidth < numlen)
235 fieldWidth = numlen;
236 if (!leftPad) {
237 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
238 putch(bufptr, zeroPad ? '0' : ' ');
239 }
240 }
241 for (unsigned i = 0; i < numlen; ++i) {
242 putch(bufptr, buf[i]);
243 }
244 if (leftPad) {
245 for (unsigned i = 0; i < fieldWidth - numlen; ++i) {
246 putch(bufptr, ' ');
247 }
248 }
249
250 return fieldWidth;
251}
252
253template<typename PutChFunc>
254[[gnu::always_inline]] inline int print_string(PutChFunc putch, char*& bufptr, const char* str, bool left_pad, size_t field_width, bool dot)
255{
256 size_t len = strlen(str);
257 if (!dot && (!field_width || field_width < len))
258 field_width = len;
259 size_t pad_amount = field_width > len ? field_width - len : 0;
260
261 if (!left_pad) {
262 for (size_t i = 0; i < pad_amount; ++i)
263 putch(bufptr, ' ');
264 }
265 for (size_t i = 0; i < min(len, field_width); ++i) {
266 putch(bufptr, str[i]);
267 }
268 if (left_pad) {
269 for (size_t i = 0; i < pad_amount; ++i)
270 putch(bufptr, ' ');
271 }
272 return field_width;
273}
274
275template<typename PutChFunc>
276[[gnu::always_inline]] inline int print_signed_number(PutChFunc putch, char*& bufptr, int number, bool leftPad, bool zeroPad, u32 fieldWidth, bool always_sign)
277{
278 if (number < 0) {
279 putch(bufptr, '-');
280 return print_number(putch, bufptr, 0 - number, leftPad, zeroPad, fieldWidth) + 1;
281 }
282 if (always_sign)
283 putch(bufptr, '+');
284 return print_number(putch, bufptr, number, leftPad, zeroPad, fieldWidth);
285}
286
287template<typename PutChFunc>
288[[gnu::always_inline]] inline int printf_internal(PutChFunc putch, char* buffer, const char*& fmt, va_list ap)
289{
290 const char* p;
291
292 int ret = 0;
293 char* bufptr = buffer;
294
295 for (p = fmt; *p; ++p) {
296 bool left_pad = false;
297 bool zeroPad = false;
298 bool dot = false;
299 unsigned fieldWidth = 0;
300 unsigned fraction_length = 0;
301 unsigned long_qualifiers = 0;
302 bool size_qualifier = false;
303 (void)size_qualifier;
304 bool alternate_form = 0;
305 bool always_sign = false;
306 if (*p == '%' && *(p + 1)) {
307 one_more:
308 ++p;
309 if (*p == '.') {
310 dot = true;
311 if (*(p + 1))
312 goto one_more;
313 }
314 if (*p == '-') {
315 left_pad = true;
316 if (*(p + 1))
317 goto one_more;
318 }
319 if (*p == '+') {
320 always_sign = true;
321 if (*(p + 1))
322 goto one_more;
323 }
324 if (!zeroPad && !fieldWidth && *p == '0') {
325 zeroPad = true;
326 if (*(p + 1))
327 goto one_more;
328 }
329 if (*p >= '0' && *p <= '9') {
330 if (!dot) {
331 fieldWidth *= 10;
332 fieldWidth += *p - '0';
333 if (*(p + 1))
334 goto one_more;
335 } else {
336 fraction_length *= 10;
337 fraction_length += *p - '0';
338 if (*(p + 1))
339 goto one_more;
340 }
341 }
342 if (*p == '*') {
343 fieldWidth = va_arg(ap, int);
344 if (*(p + 1))
345 goto one_more;
346 }
347 if (*p == 'l') {
348 ++long_qualifiers;
349 if (*(p + 1))
350 goto one_more;
351 }
352 if (*p == 'z') {
353 size_qualifier = true;
354 if (*(p + 1))
355 goto one_more;
356 }
357 if (*p == '#') {
358 alternate_form = true;
359 if (*(p + 1))
360 goto one_more;
361 }
362 switch (*p) {
363 case 's': {
364 const char* sp = va_arg(ap, const char*);
365 ret += print_string(putch, bufptr, sp ? sp : "(null)", left_pad, fieldWidth, dot);
366 } break;
367
368 case 'd':
369 case 'i':
370 if (long_qualifiers >= 2)
371 ret += print_i64(putch, bufptr, va_arg(ap, i64), left_pad, zeroPad, fieldWidth);
372 else
373 ret += print_signed_number(putch, bufptr, va_arg(ap, int), left_pad, zeroPad, fieldWidth, always_sign);
374 break;
375
376 case 'u':
377 if (long_qualifiers >= 2)
378 ret += print_u64(putch, bufptr, va_arg(ap, u64), left_pad, zeroPad, fieldWidth);
379 else
380 ret += print_number(putch, bufptr, va_arg(ap, u32), left_pad, zeroPad, fieldWidth);
381 break;
382
383 case 'Q':
384 ret += print_u64(putch, bufptr, va_arg(ap, u64), left_pad, zeroPad, fieldWidth);
385 break;
386
387 case 'q':
388 ret += print_hex(putch, bufptr, va_arg(ap, u64), false, false, left_pad, zeroPad, 16);
389 break;
390
391#if !defined(BOOTSTRAPPER) && !defined(KERNEL)
392 case 'g':
393 case 'f':
394 ret += print_double(putch, bufptr, va_arg(ap, double), left_pad, zeroPad, fieldWidth, fraction_length);
395 break;
396#endif
397
398 case 'o':
399 if (alternate_form) {
400 putch(bufptr, '0');
401 ++ret;
402 }
403 ret += print_octal_number(putch, bufptr, va_arg(ap, u32), left_pad, zeroPad, fieldWidth);
404 break;
405
406 case 'X':
407 case 'x':
408 ret += print_hex(putch, bufptr, va_arg(ap, u32), *p == 'X', alternate_form, left_pad, zeroPad, fieldWidth);
409 break;
410
411 case 'w':
412 ret += print_hex(putch, bufptr, va_arg(ap, int), false, alternate_form, false, true, 4);
413 break;
414
415 case 'b':
416 ret += print_hex(putch, bufptr, va_arg(ap, int), false, alternate_form, false, true, 2);
417 break;
418
419 case 'c': {
420 char s[2] { (char)va_arg(ap, int), 0 };
421 ret += print_string(putch, bufptr, s, left_pad, fieldWidth, dot);
422 } break;
423
424 case '%':
425 putch(bufptr, '%');
426 ++ret;
427 break;
428
429 case 'P':
430 case 'p':
431 ret += print_hex(putch, bufptr, va_arg(ap, u32), *p == 'P', true, false, true, 8);
432 break;
433
434 case 'n':
435 *va_arg(ap, int*) = ret;
436 break;
437
438 default:
439 dbg() << "printf_internal: Unimplemented format specifier " << *p << " (fmt: " << fmt << ")";
440 }
441 } else {
442 putch(bufptr, *p);
443 ++ret;
444 }
445 }
446 return ret;
447}