1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include <mos/mos_global.h>
4#include <mos/moslib_global.h>
5#include <mos_stdio.h>
6#include <mos_stdlib.h>
7#include <mos_string.h>
8
9typedef enum
10{
11 LM_none,
12 // char
13 LM_hh,
14 // short
15 LM__h,
16 // long
17 LM__l,
18 // long long
19 LM_ll,
20 // long double
21 LM__L,
22 // intmax_t
23 LM__j,
24 // size_t
25 LM__z,
26 // ptrdiff_t
27 LM__t,
28} length_modifier_t;
29
30typedef struct
31{
32 bool left_aligned; // -
33 bool show_sign; // +
34 bool space_if_positive; // prepend space if positive
35 bool pad_with_zero; // padding with zero
36
37 // For g and G types, trailing zeros are not removed.
38 // For f, F, e, E, g, G types, the output always contains a decimal point.
39 // For o, x, X types, the text 0, 0x, 0X, respectively, is prepended to non-zero numbers.
40 bool hash;
41
42 // POSIX extension '\'' not implemented
43 // glibc 2.2 extension 'I' not implemented
44
45 // If the converted value has fewer characters than the field width, it will be padded with spaces on the left
46 // (or right, if the left-adjustment flag has been given).
47 // A negative field width is taken as a '-' flag followed by a positive field width.
48 // In no case does a nonexistent or small field width cause truncation of a field;
49 // if the result of a conversion is wider than the field width, the field is expanded to contain the conversion result.
50 s32 minimum_width;
51
52 bool has_explicit_precision;
53 // `d, i, o, u, x, X` -> The minimum number of digits to appear;
54 // `a, A, e, E, f, F` -> The number of digits to appear after the radix character;
55 // `g, G` -> The maximum number of significant digits;
56 // `s, S` -> The maximum number of characters to be printed from a string;
57 s32 precision;
58
59 length_modifier_t length;
60} printf_flags_t;
61
62typedef struct
63{
64 va_list real;
65} va_list_ptrwrappper_t;
66
67static size_t parse_printf_flags(const char *format, printf_flags_t *pflags, va_list_ptrwrappper_t *args)
68{
69 const char *start = format;
70#define goto_next_char() (format++)
71#define current (*format)
72
73 while (1)
74 {
75 switch (current)
76 {
77 case '-': pflags->left_aligned = true; break;
78 case '+': pflags->show_sign = true; break;
79 case ' ': pflags->space_if_positive = true; break;
80 case '#': pflags->hash = true; break;
81 // We can read multiple 0s and leave the 'width' field empty, 0-width is meaningless.
82 case '0': pflags->pad_with_zero = true; break;
83 default: goto flag_parse_done;
84 }
85 goto_next_char();
86 };
87
88flag_parse_done:
89 // width
90 pflags->minimum_width = 0;
91 if (current == '*')
92 {
93 pflags->minimum_width = va_arg(args->real, s32), goto_next_char();
94 if (pflags->minimum_width < 0)
95 {
96 // A negative field width is taken as a '-' flag followed by a positive field width
97 pflags->left_aligned = true;
98 pflags->minimum_width = -pflags->minimum_width;
99 }
100 }
101 else
102 {
103 while ('0' <= current && current <= '9')
104 pflags->minimum_width = (pflags->minimum_width * 10 + (current - '0')), goto_next_char();
105 }
106
107 // precision
108 pflags->precision = 0;
109 if (current == '.')
110 {
111 pflags->has_explicit_precision = true;
112 goto_next_char();
113 if (current == '*')
114 {
115 pflags->precision = va_arg(args->real, s32), goto_next_char();
116 // A negative precision argument is taken as if the precision were omitted.
117 if (pflags->precision < 0)
118 pflags->precision = 0, pflags->has_explicit_precision = false;
119 }
120 else
121 {
122 while ('0' <= current && current <= '9')
123 pflags->precision = pflags->precision * 10 + current - '0', goto_next_char();
124 }
125 }
126
127 // length enums
128 if (current == 'h')
129 {
130 goto_next_char();
131 if (current == 'h')
132 pflags->length = LM_hh, goto_next_char();
133 else
134 pflags->length = LM__h;
135 }
136 else if (current == 'l')
137 {
138 goto_next_char();
139 if (current == 'l')
140 pflags->length = LM_ll, goto_next_char();
141 else
142 {
143#if MOS_LP64
144 pflags->length = LM_ll; // 64-bit long
145#else
146#error "Unknown long size"
147#endif
148 }
149 }
150 else if (current == 'L')
151 {
152 goto_next_char();
153 pflags->length = LM__L;
154 }
155 else if (current == 'j')
156 {
157 goto_next_char();
158 pflags->length = LM__j;
159 }
160 else if (current == 'z')
161 {
162 goto_next_char();
163 pflags->length = LM__z;
164 }
165 else if (current == 't')
166 {
167 goto_next_char();
168 pflags->length = LM__t;
169 }
170
171#undef goto_next_char
172#undef current
173
174 if (unlikely(pflags->left_aligned && pflags->pad_with_zero))
175 {
176 pflags->pad_with_zero = false;
177 mos_warn("printf: '0' flag is ignored by the '-' flag");
178 }
179
180 if (unlikely(pflags->show_sign && pflags->space_if_positive))
181 {
182 pflags->space_if_positive = false;
183 mos_warn("printf: ' ' flag is ignored by the '+' flag");
184 }
185
186 return format - start;
187}
188
189// writes a character into the buffer, and increases the buffer pointer
190should_inline __nodiscard int buf_putchar(char *buf, char c, size_t *size_left)
191{
192 if (*size_left > 0)
193 {
194 *buf = c;
195 (*size_left)--;
196 }
197
198 return 1;
199}
200
201static const char *const lower_hex_digits = "0123456789abcdef";
202static const char *const upper_hex_digits = "0123456789ABCDEF";
203
204#define wrap_printed(x) \
205 do \
206 { \
207 const size_t _printed = x; \
208 buf += _printed, ret += _printed; \
209 } while (0)
210
211// ! prints d, i, o, u, x, and X
212static int printf_diouxX(char *buf, u64 number, printf_flags_t *pflags, char conv, size_t *size_left)
213{
214 size_t ret = 0;
215 MOS_LIB_ASSERT(conv == 'd' || conv == 'i' || conv == 'o' || conv == 'u' || conv == 'x' || conv == 'X');
216 MOS_LIB_ASSERT(pflags->precision >= 0);
217 MOS_LIB_ASSERT(pflags->minimum_width >= 0);
218
219 if (conv == 'd' || conv == 'i' || conv == 'u')
220 {
221 if (unlikely(pflags->hash))
222 {
223 mos_warn("'#' flag is ignored in d, i mode");
224 pflags->hash = false;
225 }
226 }
227
228 enum
229 {
230 BASE_8 = 8,
231 BASE_10 = 10,
232 BASE_16 = 16,
233 } base = BASE_10;
234 bool upper_case = false;
235 const char *hex_digits = NULL;
236
237 bool is_unsigned_ouxX = conv == 'o' || conv == 'u' || conv == 'x' || conv == 'X';
238 if (is_unsigned_ouxX)
239 {
240 if (unlikely(pflags->show_sign))
241 {
242 mos_warn("'+' flag is ignored in unsigned mode");
243 pflags->show_sign = false;
244 }
245 if (unlikely(pflags->space_if_positive))
246 {
247 mos_warn("' ' flag is ignored in unsigned mode");
248 pflags->space_if_positive = false;
249 }
250
251 base = (conv == 'o' ? BASE_8 : (conv == 'u' ? BASE_10 : BASE_16));
252 upper_case = conv == 'X';
253 hex_digits = upper_case ? upper_hex_digits : lower_hex_digits;
254
255 switch (pflags->length)
256 {
257 case LM_hh: number = (u8) number; break;
258 case LM__h: number = (u16) number; break;
259 case LM_none:
260 case LM__l: number = (u32) number; break;
261 case LM_ll: number = (u64) number; break;
262 case LM__z: number = (size_t) number; break;
263 case LM__t: number = (ptrdiff_t) number; break;
264 case LM__L: number = (u64) number; break;
265 case LM__j:
266 default: MOS_LIB_UNREACHABLE();
267 }
268 }
269
270 // If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0 flag is ignored.
271 if (pflags->has_explicit_precision && pflags->pad_with_zero)
272 pflags->pad_with_zero = false;
273
274 // The default precision is 1 for d, i, o, u, x, and X
275 if (!pflags->has_explicit_precision)
276 pflags->precision = 1;
277
278 char num_prefix_buf[5] = { 0 };
279 char num_content_buf[32] = { 0 };
280 size_t num_content_len = 32;
281
282 // Setup prefixes.
283 if (base == BASE_10 && !is_unsigned_ouxX)
284 {
285 bool is_negative = ((s64) number) < 0;
286 if (is_negative)
287 number = -(s64) number, num_prefix_buf[0] = '-';
288 else if (pflags->show_sign)
289 num_prefix_buf[0] = '+'; // 0 is positive too !!!!!
290 else if (pflags->space_if_positive)
291 num_prefix_buf[0] = ' ';
292 }
293 else if (base == BASE_16 && pflags->hash)
294 {
295 // '#' flag turns on prefix '0x' or '0X' for hexadecimal conversions.
296 // For x and X conversions, a nonzero result has the string "0x" (or "0X" for X conversions) prepended to it.
297 if (number != 0)
298 {
299 num_prefix_buf[0] = '0';
300 num_prefix_buf[1] = upper_case ? 'X' : 'x';
301 }
302 }
303 else if (base == BASE_8 && pflags->hash)
304 {
305 // ! BEGIN SPECIAL CASE for base 8 + '#' flag
306 // to be handled later, when we are printing the number
307 // ! END SPECIAL CASE
308 }
309
310 // Print the number.
311 if (number == 0)
312 {
313 // When 0 is printed with an explicit precision 0, the output is empty.
314 // so only print a '0' if the precision is **not** 0.
315 if (pflags->precision != 0)
316 num_content_buf[0] = '0';
317 }
318 else
319 {
320 char *pnumberbuf = num_content_buf;
321 switch (base)
322 {
323 case BASE_8:
324 case BASE_10:
325 while (number > 0)
326 pnumberbuf += buf_putchar(buf: pnumberbuf, c: '0' + (char) (number % base), size_left: &num_content_len), number /= base;
327 break;
328 case BASE_16:
329 while (number > 0)
330 pnumberbuf += buf_putchar(buf: pnumberbuf, c: hex_digits[number % 16], size_left: &num_content_len), number /= 16;
331 break;
332 default: MOS_LIB_UNREACHABLE();
333 }
334 }
335
336 s32 n_digits = strlen(str: num_content_buf);
337
338 // ! BEGIN SPECIAL CASE for base 8 + '#' flag
339 if (base == BASE_8 && pflags->hash)
340 {
341 // if there's no padding needed, we'll have to add the '0' prefix
342 if (pflags->precision - n_digits <= 0 && num_content_buf[0] != '0')
343 num_prefix_buf[0] = '0';
344 }
345 // ! END SPECIAL CASE
346
347 s32 precision_padding = MAX(pflags->precision - n_digits, 0);
348 s32 width_to_pad = MAX(pflags->minimum_width - strlen(num_prefix_buf) - precision_padding - n_digits, 0u);
349
350 char *pnum_prefix = num_prefix_buf;
351 char *pnum_content = num_content_buf + n_digits;
352 if (pflags->left_aligned)
353 {
354 while (*pnum_prefix)
355 wrap_printed(buf_putchar(buf, *pnum_prefix++, size_left));
356 while (precision_padding-- > 0)
357 wrap_printed(buf_putchar(buf, '0', size_left));
358 while (pnum_content > num_content_buf)
359 wrap_printed(buf_putchar(buf, *--pnum_content, size_left));
360 while (width_to_pad-- > 0)
361 wrap_printed(buf_putchar(buf, ' ', size_left));
362 }
363 else
364 {
365 if (pflags->pad_with_zero)
366 {
367 // zero should be after the sign
368 while (*pnum_prefix)
369 wrap_printed(buf_putchar(buf, *pnum_prefix++, size_left));
370 while (width_to_pad-- > 0)
371 wrap_printed(buf_putchar(buf, '0', size_left));
372 }
373 else
374 {
375 // space should be before the sign
376 while (width_to_pad-- > 0)
377 wrap_printed(buf_putchar(buf, ' ', size_left));
378 while (*pnum_prefix)
379 wrap_printed(buf_putchar(buf, *pnum_prefix++, size_left));
380 }
381 while (precision_padding-- > 0)
382 wrap_printed(buf_putchar(buf, '0', size_left));
383 while (pnum_content > num_content_buf)
384 wrap_printed(buf_putchar(buf, *--pnum_content, size_left));
385 }
386 return ret;
387}
388
389static int printf_cs(char *buf, const char *data, printf_flags_t *pflags, char conv, size_t *psize_left)
390{
391 size_t ret = 0;
392 if (data == NULL)
393 data = "(null)";
394 MOS_LIB_ASSERT(conv == 'c' || conv == 's');
395 MOS_LIB_ASSERT(pflags->precision >= 0);
396 MOS_LIB_ASSERT(pflags->minimum_width >= 0);
397
398 if (unlikely(pflags->hash))
399 {
400 mos_warn("printf: '#' flag is ignored in 'c' and 's' mode.");
401 pflags->hash = false;
402 }
403
404 if (unlikely(pflags->pad_with_zero))
405 {
406 mos_warn("printf: '0' flag is ignored in 'c' and 's' mode.");
407 pflags->pad_with_zero = false;
408 }
409
410 if (unlikely(pflags->show_sign))
411 {
412 mos_warn("printf: '+' flag is ignored in 'c' and 's' mode.");
413 pflags->show_sign = false;
414 }
415
416 if (unlikely(pflags->space_if_positive))
417 {
418 mos_warn("printf: ' ' flag is ignored in 'c' and 's' mode.");
419 pflags->space_if_positive = false;
420 }
421
422 if (unlikely(conv == 'c' && pflags->has_explicit_precision))
423 {
424 mos_warn("printf: precision is ignored in 'c' mode.");
425 pflags->has_explicit_precision = false;
426 pflags->precision = 0;
427 }
428
429 s32 printed_len = conv == 'c' ? 1 : strlen(str: data);
430
431 // If presition is given, the string is truncated to this length.
432 if (pflags->has_explicit_precision)
433 printed_len = MIN(printed_len, pflags->precision);
434
435 s32 width_to_pad = MAX(pflags->minimum_width - printed_len, 0);
436
437 if (pflags->left_aligned)
438 {
439 while (printed_len-- > 0)
440 wrap_printed(buf_putchar(buf, *data++, psize_left));
441 while (width_to_pad-- > 0)
442 wrap_printed(buf_putchar(buf, ' ', psize_left));
443 }
444 else
445 {
446 while (width_to_pad-- > 0)
447 wrap_printed(buf_putchar(buf, ' ', psize_left));
448 while (printed_len-- > 0)
449 wrap_printed(buf_putchar(buf, *data++, psize_left));
450 }
451
452 return ret;
453}
454
455int vsnprintf(char *buf, size_t size, const char *format, va_list _args)
456{
457 size_t ret = 0;
458 if (size == 0)
459 return 0; // nothing to do
460
461 va_list_ptrwrappper_t args;
462 va_copy(args.real, _args);
463
464 for (; *format; format++)
465 {
466 if (*format != '%')
467 {
468 wrap_printed(buf_putchar(buf, *format, &size));
469 continue;
470 }
471
472 // skip '%'
473 format++;
474
475 // parse flags
476 printf_flags_t flags = { 0 };
477 format += parse_printf_flags(format, pflags: &flags, args: &args);
478
479 switch (*format)
480 {
481 case 'd':
482 case 'i':
483 case 'o':
484 case 'u':
485 case 'x':
486 case 'X':
487 {
488 // print a signed integer
489 u64 value = 0;
490 switch (flags.length)
491 {
492 case LM_hh: value = (s8) va_arg(args.real, s32); break;
493 case LM__h: value = (s16) va_arg(args.real, s32); break;
494 case LM_none:
495 case LM__l: value = va_arg(args.real, s32); break;
496 case LM_ll: value = va_arg(args.real, s64); break;
497 case LM__z: value = va_arg(args.real, size_t); break;
498 case LM__t: value = va_arg(args.real, ptrdiff_t); break;
499 case LM__L: value = va_arg(args.real, s64); break;
500 case LM__j:
501 default: MOS_LIB_UNREACHABLE();
502 }
503 wrap_printed(printf_diouxX(buf, value, &flags, *format, &size));
504 break;
505 }
506
507 // default precision ; for e, E, f, g, and G it is 0.
508 case 'f':
509 case 'F':
510 {
511 // print a floating point number
512 MOS_LIB_UNIMPLEMENTED("printf: %f / %F");
513 break;
514 }
515 case 'e':
516 case 'E':
517 {
518 // print a floating point number in scientific notation
519 MOS_LIB_UNIMPLEMENTED("printf: %e / %E");
520 break;
521 }
522 case 'g':
523 case 'G':
524 {
525 // print a floating point number in scientific notation
526 MOS_LIB_UNIMPLEMENTED("printf: %g / %G");
527 break;
528 }
529 case 's':
530 {
531 // print string
532 const char *string = va_arg(args.real, char *);
533 wrap_printed(printf_cs(buf, string, &flags, *format, &size));
534 break;
535 }
536 case 'c':
537 {
538 // print a character
539 char value = (char) va_arg(args.real, s32);
540 wrap_printed(printf_cs(buf, &value, &flags, *format, &size));
541 break;
542 }
543 case 'p':
544 {
545 // print a pointer
546 ptr_t value = (ptr_t) va_arg(args.real, void *);
547#ifdef __MOS_KERNEL__
548 // print special kernel data types
549 extern size_t vsnprintf_do_pointer_kernel(char *buf, size_t *size, const char **pformat, ptr_t ptr);
550 const size_t s = vsnprintf_do_pointer_kernel(buf, size: &size, pformat: &format, ptr: value);
551 wrap_printed(s);
552 if (s)
553 break;
554#endif
555 wrap_printed(buf_putchar(buf, '0', &size));
556 wrap_printed(buf_putchar(buf, 'x', &size));
557 flags.length = LM_ll;
558 wrap_printed(printf_diouxX(buf, value, &flags, 'x', &size));
559 break;
560 }
561 case 'a':
562 case 'A':
563 {
564 // print a hexadecimal number in ASCII
565 MOS_LIB_UNIMPLEMENTED("printf: %a / %A");
566 break;
567 }
568 case 'n':
569 {
570 // print the number of characters printed
571 MOS_LIB_UNIMPLEMENTED("printf: %n");
572 break;
573 }
574 case '%':
575 {
576 // print a '%'
577 wrap_printed(buf_putchar(buf, '%', &size));
578 break;
579 }
580 case '\0':
581 {
582 // end of format string
583 mos_warn("printf: incomplete format specifier");
584 goto end;
585 }
586 default:
587 {
588 mos_warn("printf: unknown format specifier");
589 wrap_printed(buf_putchar(buf, '%', &size));
590 wrap_printed(buf_putchar(buf, *format, &size));
591 break;
592 }
593 }
594 }
595end:
596 buf += buf_putchar(buf, c: 0, size_left: &size);
597
598 va_end(args.real);
599 return ret;
600}
601