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