1 //===-- Hexadecimal Converter for printf ------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_HEX_CONVERTER_H
10 #define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_HEX_CONVERTER_H
11
12 #include "src/stdio/printf_core/converter_utils.h"
13 #include "src/stdio/printf_core/core_structs.h"
14 #include "src/stdio/printf_core/writer.h"
15
16 #include <inttypes.h>
17 #include <stddef.h>
18
19 namespace __llvm_libc {
20 namespace printf_core {
21
convert_hex(Writer * writer,const FormatSection & to_conv)22 int convert_hex(Writer *writer, const FormatSection &to_conv) {
23 // This approximates the number of digits it takes to represent a hexadecimal
24 // value of a certain number of bits. Each hex digit represents 4 bits, so the
25 // exact value is the number of bytes multiplied by 2.
26 static constexpr size_t BUFF_LEN = sizeof(uintmax_t) * 2;
27 uintmax_t num = to_conv.conv_val_raw;
28 char buffer[BUFF_LEN];
29
30 // All of the characters will be defined relative to variable a, which will be
31 // the appropriate case based on the name of the conversion.
32 char a;
33 if (to_conv.conv_name == 'x')
34 a = 'a';
35 else
36 a = 'A';
37
38 num = apply_length_modifier(num, to_conv.length_modifier);
39
40 // buff_cur can never reach 0, since the buffer is sized to always be able to
41 // contain the whole integer. This means that bounds checking it should be
42 // unnecessary.
43 size_t buff_cur = BUFF_LEN;
44 for (; num > 0 /* && buff_cur > 0 */; --buff_cur, num /= 16)
45 buffer[buff_cur - 1] =
46 ((num % 16) > 9) ? ((num % 16) - 10 + a) : ((num % 16) + '0');
47
48 size_t digits_written = BUFF_LEN - buff_cur;
49
50 // these are signed to prevent underflow due to negative values. The eventual
51 // values will always be non-negative.
52 int zeroes;
53 int spaces;
54
55 // prefix is "0x"
56 int prefix_len;
57 char prefix[2];
58 if ((to_conv.flags & FormatFlags::ALTERNATE_FORM) ==
59 FormatFlags::ALTERNATE_FORM) {
60 prefix_len = 2;
61 prefix[0] = '0';
62 prefix[1] = a + ('x' - 'a');
63 } else {
64 prefix_len = 0;
65 prefix[0] = 0;
66 }
67
68 // negative precision indicates that it was not specified.
69 if (to_conv.precision < 0) {
70 if ((to_conv.flags &
71 (FormatFlags::LEADING_ZEROES | FormatFlags::LEFT_JUSTIFIED)) ==
72 FormatFlags::LEADING_ZEROES) {
73 // if this conv has flag 0 but not - and no specified precision, it's
74 // padded with 0's instead of spaces identically to if precision =
75 // min_width - (2 if prefix). For example: ("%#04x", 15) -> "0x0f"
76 zeroes = to_conv.min_width - digits_written - prefix_len;
77 if (zeroes < 0)
78 zeroes = 0;
79 spaces = 0;
80 } else if (digits_written < 1) {
81 // if no precision is specified, precision defaults to 1. This means that
82 // if the integer passed to the conversion is 0, a 0 will be printed.
83 // Example: ("%3x", 0) -> " 0"
84 zeroes = 1;
85 spaces = to_conv.min_width - zeroes - prefix_len;
86 } else {
87 // If there are enough digits to pass over the precision, just write the
88 // number, padded by spaces.
89 zeroes = 0;
90 spaces = to_conv.min_width - digits_written - prefix_len;
91 }
92 } else {
93 // if precision was specified, possibly write zeroes, and possibly write
94 // spaces. Example: ("%5.4x", 0x10000) -> "10000"
95 // If the check for if zeroes is negative was not there, spaces would be
96 // incorrectly evaluated as 1.
97 zeroes = to_conv.precision - digits_written; // a negative value means 0
98 if (zeroes < 0)
99 zeroes = 0;
100 spaces = to_conv.min_width - zeroes - digits_written - prefix_len;
101 }
102 if (spaces < 0)
103 spaces = 0;
104
105 if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
106 FormatFlags::LEFT_JUSTIFIED) {
107 // if left justified it goes prefix zeroes digits spaces
108 if (prefix[0] != 0)
109 RET_IF_RESULT_NEGATIVE(writer->write(prefix, 2));
110 if (zeroes > 0)
111 RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
112 if (digits_written > 0)
113 RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, digits_written));
114 if (spaces > 0)
115 RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
116 } else {
117 // else it goes spaces prefix zeroes digits
118 if (spaces > 0)
119 RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
120 if (prefix[0] != 0)
121 RET_IF_RESULT_NEGATIVE(writer->write(prefix, 2));
122 if (zeroes > 0)
123 RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
124 if (digits_written > 0)
125 RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, digits_written));
126 }
127 return WRITE_OK;
128 }
129
130 } // namespace printf_core
131 } // namespace __llvm_libc
132
133 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_HEX_CONVERTER_H
134