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.
This commit is contained in:
asynts 2020-09-26 15:26:00 +02:00 committed by Andreas Kling
parent 8af67210cf
commit 2111fc5f63
Notes: sideshowbarker 2024-07-19 02:12:46 +09:00
2 changed files with 291 additions and 0 deletions

View File

@ -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

124
AK/Tests/TestPrintf.cpp Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/TestSuite.h>
#include <AK/PrintfImplementation.h>
#include <AK/StringBuilder.h>
TEST_CASE(format_unsigned_with_internal_implementation)
{
Array<u8, 128> buffer;
size_t used = 0;
used = PrintfImplementation::convert_unsigned_to_string(12341234, buffer, 10, false);
EXPECT_EQ(StringView { buffer.span().trim(used) }, "12341234");
used = PrintfImplementation::convert_unsigned_to_string(12341234, buffer, 16, false);
EXPECT_EQ(StringView { buffer.span().trim(used) }, "bc4ff2");
used = PrintfImplementation::convert_unsigned_to_string(12341234, buffer, 16, true);
EXPECT_EQ(StringView { buffer.span().trim(used) }, "BC4FF2");
used = PrintfImplementation::convert_unsigned_to_string(0, buffer, 10, true);
EXPECT_EQ(StringView { buffer.span().trim(used) }, "0");
used = PrintfImplementation::convert_unsigned_to_string(NumericLimits<u64>::max(), buffer, 10, true);
EXPECT_EQ(StringView { buffer.span().trim(used) }, "18446744073709551615");
}
TEST_CASE(format_unsigned_just_pass_through)
{
StringBuilder builder;
size_t used = 0;
builder.clear();
used = PrintfImplementation::convert_unsigned_to_string(12341234, builder);
EXPECT_EQ(used, 8u);
EXPECT_EQ(builder.to_string(), "12341234");
builder.clear();
used = PrintfImplementation::convert_unsigned_to_string(12341234, builder, 16);
EXPECT_EQ(used, 6u);
EXPECT_EQ(builder.to_string(), "bc4ff2");
builder.clear();
used = PrintfImplementation::convert_unsigned_to_string(12341234, builder, 16, false, true);
EXPECT_EQ(used, 6u);
EXPECT_EQ(builder.to_string(), "BC4FF2");
}
TEST_CASE(format_unsigned)
{
StringBuilder builder;
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Right, 4, '*', PrintfImplementation::SignMode::OnlyIfNeeded);
EXPECT_EQ(builder.to_string(), "0042");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Left, 4, '*', PrintfImplementation::SignMode::OnlyIfNeeded);
EXPECT_EQ(builder.to_string(), "42**");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Center, 4, '*', PrintfImplementation::SignMode::OnlyIfNeeded);
EXPECT_EQ(builder.to_string(), "*42*");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Center, 9, '*', PrintfImplementation::SignMode::OnlyIfNeeded);
EXPECT_EQ(builder.to_string(), "***42****");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Center, 9, '*', PrintfImplementation::SignMode::Reserved);
EXPECT_EQ(builder.to_string(), "*** 42***");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Left, 4, '*', PrintfImplementation::SignMode::Always, true);
EXPECT_EQ(builder.to_string(), "-42*");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Center, 4, '*', PrintfImplementation::SignMode::Reserved, true);
EXPECT_EQ(builder.to_string(), "-42*");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(42, builder, 10, false, false, true, PrintfImplementation::Align::Right, 4, '*', PrintfImplementation::SignMode::OnlyIfNeeded, true);
EXPECT_EQ(builder.to_string(), "-042");
builder.clear();
PrintfImplementation::convert_unsigned_to_string(32, builder, 16, true, false, true, PrintfImplementation::Align::Right, 8, '*', PrintfImplementation::SignMode::OnlyIfNeeded, true);
EXPECT_EQ(builder.to_string(), "-0x00020");
}
TEST_CASE(format_signed)
{
StringBuilder builder;
builder.clear();
PrintfImplementation::convert_signed_to_string(42, builder, 10, false, false, false, PrintfImplementation::Align::Right, 8, '/', PrintfImplementation::SignMode::OnlyIfNeeded);
EXPECT_EQ(builder.to_string(), "//////42");
}
TEST_MAIN(Printf)