summaryrefslogtreecommitdiff
path: root/AK/PrintfImplementation.h
diff options
context:
space:
mode:
authorasynts <asynts@gmail.com>2020-09-26 15:26:00 +0200
committerAndreas Kling <kling@serenityos.org>2020-09-26 17:19:04 +0200
commit2111fc5f63822c05bc88b58ad85cb897fa79e3fb (patch)
tree8c1beee455db12d65a252452a1c798e7f568afc3 /AK/PrintfImplementation.h
parent8af67210cfe3b9d82d95d9094320180800854fae (diff)
downloadserenity-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.h167
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