diff options
author | asynts <asynts@gmail.com> | 2020-09-26 15:26:00 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-09-26 17:19:04 +0200 |
commit | 2111fc5f63822c05bc88b58ad85cb897fa79e3fb (patch) | |
tree | 8c1beee455db12d65a252452a1c798e7f568afc3 /AK/PrintfImplementation.h | |
parent | 8af67210cfe3b9d82d95d9094320180800854fae (diff) | |
download | serenity-2111fc5f63822c05bc88b58ad85cb897fa79e3fb.zip |
AK+Format: Add new integer to string backend.
I put this into the <AK/PrintfImplementation.h> header in the hope that
it could be re-used by the printf implementation. That would not be
super trivial though, so I am not doing that now.
Diffstat (limited to 'AK/PrintfImplementation.h')
-rw-r--r-- | AK/PrintfImplementation.h | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/AK/PrintfImplementation.h b/AK/PrintfImplementation.h index c1ee76f473..4fd8665ece 100644 --- a/AK/PrintfImplementation.h +++ b/AK/PrintfImplementation.h @@ -29,6 +29,7 @@ #include <AK/Assertions.h> #include <AK/LogStream.h> #include <AK/StdLibExtras.h> +#include <AK/StringBuilder.h> #include <AK/Types.h> #include <stdarg.h> @@ -37,6 +38,172 @@ namespace PrintfImplementation { static constexpr const char* printf_hex_digits_lower = "0123456789abcdef"; static constexpr const char* printf_hex_digits_upper = "0123456789ABCDEF"; +enum class Align { + Left, + Center, + Right, +}; + +enum class SignMode { + OnlyIfNeeded, + Always, + Reserved +}; + +// The worst case is that we have the largest 64-bit value formatted as binary number, this would take +// 65 bytes. Choosing a larger power of two won't hurt and is a bit of mitigation against out-of-bounds accesses. +inline size_t convert_unsigned_to_string(u64 value, Array<u8, 128>& buffer, u8 base, bool upper_case) +{ + ASSERT(base >= 2 && base <= 16); + + static constexpr const char* lowercase_lookup = "0123456789abcdef"; + static constexpr const char* uppercase_lookup = "0123456789ABCDEF"; + + if (value == 0) { + buffer[0] = '0'; + return 1; + } + + size_t used = 0; + while (value > 0) { + if (upper_case) + buffer[used++] = uppercase_lookup[value % base]; + else + buffer[used++] = lowercase_lookup[value % base]; + + value /= base; + } + + // Reverse the list; I came up with this logic in like three seconds so it's probably wrong in some edge case. + for (size_t i = 0; i < used / 2; ++i) + swap(buffer[i], buffer[used - i - 1]); + + return used; +} + +inline size_t convert_unsigned_to_string( + u64 value, + StringBuilder& builder, + u8 base = 10, + bool prefix = false, + bool upper_case = false, + bool zero_pad = false, + Align align = Align::Right, + size_t width = 0, + char fill = ' ', + SignMode sign_mode = SignMode::OnlyIfNeeded, + bool is_negative = false) +{ + Array<u8, 128> buffer; + + const auto used_by_significant_digits = convert_unsigned_to_string(value, buffer, base, upper_case); + size_t used_by_prefix = sign_mode == SignMode::OnlyIfNeeded ? static_cast<size_t>(is_negative) : 1; + + if (prefix) { + if (base == 8) + used_by_prefix += 1; + else if (base == 16) + used_by_prefix += 2; + else if (base == 2) + used_by_prefix += 2; + } + + const auto put_prefix = [&]() { + if (is_negative) + builder.append('-'); + else if (sign_mode == SignMode::Always) + builder.append('+'); + else if (sign_mode == SignMode::Reserved) + builder.append(' '); + + if (prefix) { + if (base == 2) { + if (upper_case) + builder.append("0B"); + else + builder.append("0b"); + } else if (base == 8) { + builder.append("0"); + } else if (base == 16) { + if (upper_case) + builder.append("0X"); + else + builder.append("0x"); + } + } + }; + const auto put_padding = [&](size_t amount, char fill) { + for (size_t i = 0; i < amount; ++i) + builder.append(fill); + }; + const auto put_digits = [&]() { + builder.append(StringView { buffer.span().trim(used_by_significant_digits) }); + }; + + const auto used_by_field = used_by_significant_digits + used_by_prefix; + const auto used_by_padding = static_cast<size_t>(max<ssize_t>(0, static_cast<ssize_t>(width) - static_cast<ssize_t>(used_by_field))); + + if (align == Align::Left) { + const auto used_by_right_padding = used_by_padding; + + put_prefix(); + put_digits(); + put_padding(used_by_right_padding, fill); + + return used_by_field + used_by_right_padding; + } + + if (align == Align::Center) { + const auto used_by_left_padding = used_by_padding / 2; + const auto used_by_right_padding = ceil_div<size_t, size_t>(used_by_padding, 2); + + put_padding(used_by_left_padding, fill); + put_prefix(); + put_digits(); + put_padding(used_by_right_padding, fill); + + return used_by_left_padding + used_by_field + used_by_right_padding; + } + + if (align == Align::Right) { + const auto used_by_left_padding = used_by_padding; + + if (zero_pad) { + put_prefix(); + put_padding(used_by_left_padding, '0'); + put_digits(); + } else { + put_padding(used_by_left_padding, fill); + put_prefix(); + put_digits(); + } + + return used_by_field + used_by_left_padding; + } + + ASSERT_NOT_REACHED(); +} + +inline size_t convert_signed_to_string( + i64 value, + StringBuilder& builder, + u8 base = 10, + bool common_prefix = false, + bool upper_case = false, + bool zero_pad = false, + Align align = Align::Right, + size_t width = 0, + char fill = ' ', + SignMode sign_mode = SignMode::OnlyIfNeeded) +{ + bool is_negative = value < 0; + + if (value < 0) + value = -value; + + return convert_unsigned_to_string(static_cast<size_t>(value), builder, base, common_prefix, upper_case, zero_pad, align, width, fill, sign_mode, is_negative); +} + #ifdef __serenity__ extern "C" size_t strlen(const char*); #else |