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 | |
9 | typedef 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 | |
30 | typedef 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 | |
62 | typedef struct |
63 | { |
64 | va_list real; |
65 | } va_list_ptrwrappper_t; |
66 | |
67 | static 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 | |
88 | flag_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 |
190 | should_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 | |
201 | static const char *const lower_hex_digits = "0123456789abcdef" ; |
202 | static 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 |
212 | static 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 | |
389 | static 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 | |
455 | int 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 | } |
595 | end: |
596 | buf += buf_putchar(buf, c: 0, size_left: &size); |
597 | |
598 | va_end(args.real); |
599 | return ret; |
600 | } |
601 | |