213 lines · c
1//===-- Integer 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-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_INT_CONVERTER_H10#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_INT_CONVERTER_H11 12#include "src/__support/CPP/span.h"13#include "src/__support/CPP/string_view.h"14#include "src/__support/ctype_utils.h"15#include "src/__support/integer_to_string.h"16#include "src/__support/macros/config.h"17#include "src/stdio/printf_core/converter_utils.h"18#include "src/stdio/printf_core/core_structs.h"19#include "src/stdio/printf_core/writer.h"20 21#include <inttypes.h>22#include <stddef.h>23 24namespace LIBC_NAMESPACE_DECL {25namespace printf_core {26 27namespace details {28 29using HexFmt = IntegerToString<uintmax_t, radix::Hex>;30using HexFmtUppercase = IntegerToString<uintmax_t, radix::Hex::Uppercase>;31using OctFmt = IntegerToString<uintmax_t, radix::Oct>;32using DecFmt = IntegerToString<uintmax_t>;33using BinFmt = IntegerToString<uintmax_t, radix::Bin>;34 35LIBC_INLINE constexpr size_t num_buf_size() {36 cpp::array<size_t, 5> sizes{37 HexFmt::buffer_size(), HexFmtUppercase::buffer_size(),38 OctFmt::buffer_size(), DecFmt::buffer_size(), BinFmt::buffer_size()};39 40 auto result = sizes[0];41 for (size_t i = 1; i < sizes.size(); i++)42 result = cpp::max(result, sizes[i]);43 return result;44}45 46LIBC_INLINE cpp::optional<cpp::string_view>47num_to_strview(uintmax_t num, cpp::span<char> bufref, char conv_name) {48 if (internal::tolower(conv_name) == 'x') {49 if (internal::islower(conv_name))50 return HexFmt::format_to(bufref, num);51 else52 return HexFmtUppercase::format_to(bufref, num);53 } else if (conv_name == 'o') {54 return OctFmt::format_to(bufref, num);55 } else if (internal::tolower(conv_name) == 'b') {56 return BinFmt::format_to(bufref, num);57 } else {58 return DecFmt::format_to(bufref, num);59 }60}61 62} // namespace details63 64template <WriteMode write_mode>65LIBC_INLINE int convert_int(Writer<write_mode> *writer,66 const FormatSection &to_conv) {67 static constexpr size_t BITS_IN_BYTE = 8;68 static constexpr size_t BITS_IN_NUM = sizeof(uintmax_t) * BITS_IN_BYTE;69 70 uintmax_t num = static_cast<uintmax_t>(to_conv.conv_val_raw);71 bool is_negative = false;72 FormatFlags flags = to_conv.flags;73 74 // If the conversion is signed, then handle negative values.75 if (to_conv.conv_name == 'd' || to_conv.conv_name == 'i') {76 // Check if the number is negative by checking the high bit. This works even77 // for smaller numbers because they're sign extended by default.78 if ((num & (uintmax_t(1) << (BITS_IN_NUM - 1))) > 0) {79 is_negative = true;80 num = -num;81 }82 } else {83 // These flags are only for signed conversions, so this removes them if the84 // conversion is unsigned.85 flags = FormatFlags(flags &86 ~(FormatFlags::FORCE_SIGN | FormatFlags::SPACE_PREFIX));87 }88 89 num =90 apply_length_modifier(num, {to_conv.length_modifier, to_conv.bit_width});91 cpp::array<char, details::num_buf_size()> buf;92 auto str = details::num_to_strview(num, buf, to_conv.conv_name);93 if (!str)94 return INT_CONVERSION_ERROR;95 96 size_t digits_written = str->size();97 98 char sign_char = 0;99 100 if (is_negative)101 sign_char = '-';102 else if ((flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)103 sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX104 else if ((flags & FormatFlags::SPACE_PREFIX) == FormatFlags::SPACE_PREFIX)105 sign_char = ' ';106 107 // These are signed to prevent underflow due to negative values. The eventual108 // values will always be non-negative.109 int zeroes;110 int spaces;111 112 // prefix is "0x" for hexadecimal, or the sign character for signed113 // conversions. Since hexadecimal is unsigned these will never conflict.114 size_t prefix_len;115 char prefix[2];116 if ((internal::tolower(to_conv.conv_name) == 'x') &&117 ((flags & FormatFlags::ALTERNATE_FORM) != 0) && num != 0) {118 prefix_len = 2;119 prefix[0] = '0';120 prefix[1] = internal::islower(to_conv.conv_name) ? 'x' : 'X';121 } else if ((internal::tolower(to_conv.conv_name) == 'b') &&122 ((flags & FormatFlags::ALTERNATE_FORM) != 0) && num != 0) {123 prefix_len = 2;124 prefix[0] = '0';125 prefix[1] = internal::islower(to_conv.conv_name) ? 'b' : 'B';126 } else {127 prefix_len = (sign_char == 0 ? 0 : 1);128 prefix[0] = sign_char;129 }130 131 // Negative precision indicates that it was not specified.132 if (to_conv.precision < 0) {133 if ((flags & (FormatFlags::LEADING_ZEROES | FormatFlags::LEFT_JUSTIFIED)) ==134 FormatFlags::LEADING_ZEROES) {135 // If this conv has flag 0 but not - and no specified precision, it's136 // padded with 0's instead of spaces identically to if precision =137 // min_width - (1 if sign_char). For example: ("%+04d", 1) -> "+001"138 zeroes =139 static_cast<int>(to_conv.min_width - digits_written - prefix_len);140 spaces = 0;141 } else {142 // If there are enough digits to pass over the precision, just write the143 // number, padded by spaces.144 zeroes = 0;145 spaces =146 static_cast<int>(to_conv.min_width - digits_written - prefix_len);147 }148 } else {149 // If precision was specified, possibly write zeroes, and possibly write150 // spaces. Example: ("%5.4d", 10000) -> "10000"151 // If the check for if zeroes is negative was not there, spaces would be152 // incorrectly evaluated as 1.153 //154 // The standard treats the case when num and precision are both zeroes as155 // special - it requires that no characters are produced. So, we adjust for156 // that special case first.157 if (num == 0 && to_conv.precision == 0)158 digits_written = 0;159 zeroes = static_cast<int>(to_conv.precision -160 digits_written); // a negative value means 0161 if (zeroes < 0)162 zeroes = 0;163 spaces = static_cast<int>(to_conv.min_width - zeroes - digits_written -164 prefix_len);165 }166 167 // The standard says that alternate form for the o conversion "increases168 // the precision, if and only if necessary, to force the first digit of the169 // result to be a zero (if the value and precision are both 0, a single 0 is170 // printed)"171 // This if checks the following conditions:172 // 1) is this an o conversion in alternate form?173 // 2) does this number has a leading zero?174 // 2a) ... because there are additional leading zeroes?175 // 2b) ... because it is just "0", unless it will not write any digits.176 const bool has_leading_zero =177 (zeroes > 0) || ((num == 0) && (digits_written != 0));178 if ((to_conv.conv_name == 'o') &&179 ((to_conv.flags & FormatFlags::ALTERNATE_FORM) != 0) &&180 !has_leading_zero) {181 zeroes = 1;182 --spaces;183 }184 185 if ((flags & FormatFlags::LEFT_JUSTIFIED) == FormatFlags::LEFT_JUSTIFIED) {186 // If left justified it goes prefix zeroes digits spaces187 if (prefix_len != 0)188 RET_IF_RESULT_NEGATIVE(writer->write({prefix, prefix_len}));189 if (zeroes > 0)190 RET_IF_RESULT_NEGATIVE(writer->write('0', zeroes));191 if (digits_written > 0)192 RET_IF_RESULT_NEGATIVE(writer->write(*str));193 if (spaces > 0)194 RET_IF_RESULT_NEGATIVE(writer->write(' ', spaces));195 } else {196 // Else it goes spaces prefix zeroes digits197 if (spaces > 0)198 RET_IF_RESULT_NEGATIVE(writer->write(' ', spaces));199 if (prefix_len != 0)200 RET_IF_RESULT_NEGATIVE(writer->write({prefix, prefix_len}));201 if (zeroes > 0)202 RET_IF_RESULT_NEGATIVE(writer->write('0', zeroes));203 if (digits_written > 0)204 RET_IF_RESULT_NEGATIVE(writer->write(*str));205 }206 return WRITE_OK;207}208 209} // namespace printf_core210} // namespace LIBC_NAMESPACE_DECL211 212#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_INT_CONVERTER_H213