diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt --- a/libc/src/stdio/printf_core/CMakeLists.txt +++ b/libc/src/stdio/printf_core/CMakeLists.txt @@ -47,6 +47,7 @@ converter_atlas.h string_converter.h char_converter.h + int_converter.h DEPENDS .writer .core_structs diff --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp --- a/libc/src/stdio/printf_core/converter.cpp +++ b/libc/src/stdio/printf_core/converter.cpp @@ -42,7 +42,7 @@ case 'd': case 'i': case 'u': - // convert_int(writer, to_conv); + convert_int(writer, to_conv); return; case 'o': // convert_oct(writer, to_conv); diff --git a/libc/src/stdio/printf_core/converter_atlas.h b/libc/src/stdio/printf_core/converter_atlas.h --- a/libc/src/stdio/printf_core/converter_atlas.h +++ b/libc/src/stdio/printf_core/converter_atlas.h @@ -20,6 +20,8 @@ #include "src/stdio/printf_core/char_converter.h" // defines convert_int +#include "src/stdio/printf_core/int_converter.h" + // defines convert_oct // defines convert_hex diff --git a/libc/src/stdio/printf_core/int_converter.h b/libc/src/stdio/printf_core/int_converter.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/int_converter.h @@ -0,0 +1,178 @@ +//===-- Integer Converter for printf ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/printf_core/core_structs.h" +#include "src/stdio/printf_core/writer.h" + +#include +#include + +namespace __llvm_libc { +namespace printf_core { + +// This approximates the number of digits it takes to represent an integer of a +// certain number of bits. The calculation is ceil(bits) / 3 +// 32 -> 11 (actually needs 10) +// 64 -> 22 (actually needs 20) +// 128 -> 43 (actually needs 39) +// it's overestimating, but not by a lot, and it means it'll support any size. +template constexpr size_t digits_to_store() { + return (((sizeof(T) * 8) + 2) / 3); +} + +template constexpr uintmax_t type_mask() { + if (sizeof(T) == sizeof(uintmax_t)) + return ~uintmax_t(0); + else + return (uintmax_t(1) << (sizeof(T) * 8)) - 1; +} + +void convert_int(Writer *writer, FormatSection to_conv) { + static constexpr size_t BITS_IN_BYTE = 8; + static constexpr size_t BITS_IN_NUM = sizeof(uintmax_t) * BITS_IN_BYTE; + static constexpr size_t buff_len = digits_to_store(); + uintmax_t num = to_conv.conv_val_raw; + char buffer[buff_len]; + bool is_negative = false; + + if (to_conv.conv_name == 'u') { + // these flags are only for signed conversions, so this removes them if the + // conversion is unsigned. + to_conv.flags = static_cast( + to_conv.flags & ~(FormatFlags::FORCE_SIGN | FormatFlags::SPACE_PREFIX)); + } else { + // Check if the number is negative by checking the high bit. This works even + // for smaller numbers because they're sign extended by default. + if ((num & (uintmax_t(1) << (BITS_IN_NUM - 1))) > 0) { + is_negative = true; + num = -num; + } + } + + switch (to_conv.length_modifier) { + case LengthModifier::none: + num = num & type_mask(); + break; + + case LengthModifier::l: + num = num & type_mask(); + break; + case LengthModifier::ll: + case LengthModifier::L: + num = num & type_mask(); + break; + case LengthModifier::h: + num = num & type_mask(); + break; + case LengthModifier::hh: + num = num & type_mask(); + break; + case LengthModifier::z: + num = num & type_mask(); + break; + case LengthModifier::t: + num = num & type_mask(); + break; + case LengthModifier::j: + // j is intmax, so no mask is necessary. + break; + } + + // buff_cur can never reach 0, since the buffer is sized to always be able to + // contain the whole integer. This means that bounds checking it should be + // unnecessary. + size_t buff_cur = buff_len - 1; + while (num > 0 /* && buff_cur > 0 */) { + char new_digit = (num % 10) + '0'; + num = num / 10; + buffer[buff_cur] = new_digit; + --buff_cur; + } + + size_t digits_written = buff_len - buff_cur - 1; + + char sign_char = 0; + + if (is_negative) + sign_char = '-'; + else if ((to_conv.flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN) + sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX + else if ((to_conv.flags & FormatFlags::SPACE_PREFIX) == + FormatFlags::SPACE_PREFIX) + sign_char = ' '; + + int sign_char_len = (sign_char == 0 ? 0 : 1); + + // these are signed to prevent underflow due to negative values. The eventual + // values will always be non-negative. + int zeroes; + int spaces; + + // negative precision indicates that it was not specified. + if (to_conv.precision < 0) { + if ((to_conv.flags & + (FormatFlags::LEADING_ZEROES | FormatFlags::LEFT_JUSTIFIED)) == + FormatFlags::LEADING_ZEROES) { + // if this conv has flag 0 but not - and no specified precision, it's + // padded with 0's instead of spaces identically to if precision = + // min_width - (1 if sign_char). For example: ("%+04d", 1) -> "+001" + zeroes = to_conv.min_width - digits_written - sign_char_len; + if (zeroes < 0) + zeroes = 0; + spaces = 0; + } else if (digits_written < 1) { + // if no precision is specified, precision defaults to 1. This means that + // if the integer passed to the conversion is 0, a 0 will be printed. + // Example: ("%3d", 0) -> " 0" + zeroes = 1; + spaces = to_conv.min_width - zeroes - sign_char_len; + } else { + // If there are enough digits to pass over the precision, just write the + // number, padded by spaces. + zeroes = 0; + spaces = to_conv.min_width - digits_written - sign_char_len; + } + } else { + // if precision was specified, possibly write zeroes, and possibly write + // spaces. Example: ("%5.4d", 10000) -> "10000" + // If the check for if zeroes is negative was not there, spaces would be + // incorrectly evaluated as 1. + zeroes = to_conv.precision - digits_written; // a negative value means 0 + if (zeroes < 0) + zeroes = 0; + spaces = to_conv.min_width - zeroes - digits_written - sign_char_len; + } + if (spaces < 0) + spaces = 0; + + if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) == + FormatFlags::LEFT_JUSTIFIED) { + // if left justified it goes sign zeroes digits spaces + if (sign_char != 0) + writer->write(&sign_char, 1); + if (zeroes > 0) + writer->write_chars('0', zeroes); + if (digits_written > 0) + writer->write(buffer + buff_cur + 1, digits_written); + if (spaces > 0) + writer->write_chars(' ', spaces); + } else { + // else it goes spaces sign zeroes digits + if (spaces > 0) + writer->write_chars(' ', spaces); + if (sign_char != 0) + writer->write(&sign_char, 1); + if (zeroes > 0) + writer->write_chars('0', zeroes); + if (digits_written > 0) + writer->write(buffer + buff_cur + 1, digits_written); + } +} + +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/printf_core/converter_test.cpp b/libc/test/src/stdio/printf_core/converter_test.cpp --- a/libc/test/src/stdio/printf_core/converter_test.cpp +++ b/libc/test/src/stdio/printf_core/converter_test.cpp @@ -13,13 +13,20 @@ #include "utils/UnitTest/Test.h" -TEST(LlvmLibcPrintfConverterTest, SimpleRawConversion) { - char str[10]; - __llvm_libc::printf_core::StringWriter str_writer(str); - __llvm_libc::printf_core::Writer writer( +class LlvmLibcPrintfConverterTest : public __llvm_libc::testing::Test { +protected: + // void SetUp() override {} + // void TearDown() override {} + + char str[60]; + __llvm_libc::printf_core::StringWriter str_writer = + __llvm_libc::printf_core::StringWriter(str); + __llvm_libc::printf_core::Writer writer = __llvm_libc::printf_core::Writer( reinterpret_cast(&str_writer), __llvm_libc::printf_core::write_to_string); +}; +TEST_F(LlvmLibcPrintfConverterTest, SimpleRawConversion) { __llvm_libc::printf_core::FormatSection raw_section; raw_section.has_conv = false; raw_section.raw_string = "abc"; @@ -33,17 +40,10 @@ ASSERT_EQ(writer.get_chars_written(), 3ull); } -TEST(LlvmLibcPrintfConverterTest, PercentConversion) { - char str[20]; - __llvm_libc::printf_core::StringWriter str_writer(str); - __llvm_libc::printf_core::Writer writer( - reinterpret_cast(&str_writer), - __llvm_libc::printf_core::write_to_string); - +TEST_F(LlvmLibcPrintfConverterTest, PercentConversion) { __llvm_libc::printf_core::FormatSection simple_conv; simple_conv.has_conv = true; - simple_conv.raw_string = "abc123"; - simple_conv.raw_len = 6; + simple_conv.raw_string = "%%"; simple_conv.conv_name = '%'; __llvm_libc::printf_core::convert(&writer, simple_conv); @@ -54,45 +54,43 @@ ASSERT_EQ(writer.get_chars_written(), 1ull); } -TEST(LlvmLibcPrintfConverterTest, CharConversion) { - char str[20]; - __llvm_libc::printf_core::StringWriter str_writer(str); - __llvm_libc::printf_core::Writer writer( - reinterpret_cast(&str_writer), - __llvm_libc::printf_core::write_to_string); - +TEST_F(LlvmLibcPrintfConverterTest, CharConversionSimple) { __llvm_libc::printf_core::FormatSection simple_conv; simple_conv.has_conv = true; - simple_conv.raw_string = "abc123"; - simple_conv.raw_len = 6; + // If has_conv is true, the raw string is ignored. They are not being parsed + // and match the actual conversion taking place so that you can compare these + // tests with other implmentations. The raw strings are completely optional. + simple_conv.raw_string = "%c"; simple_conv.conv_name = 'c'; simple_conv.conv_val_raw = 'D'; __llvm_libc::printf_core::convert(&writer, simple_conv); - str[1] = '\0'; + str_writer.terminate(); ASSERT_STREQ(str, "D"); ASSERT_EQ(writer.get_chars_written(), 1ull); +} +TEST_F(LlvmLibcPrintfConverterTest, CharConversionRightJustified) { __llvm_libc::printf_core::FormatSection right_justified_conv; right_justified_conv.has_conv = true; - right_justified_conv.raw_string = "abc123"; - right_justified_conv.raw_len = 6; + right_justified_conv.raw_string = "%4c"; right_justified_conv.conv_name = 'c'; right_justified_conv.min_width = 4; right_justified_conv.conv_val_raw = 'E'; __llvm_libc::printf_core::convert(&writer, right_justified_conv); - str[5] = '\0'; + str_writer.terminate(); - ASSERT_STREQ(str, "D E"); - ASSERT_EQ(writer.get_chars_written(), 5ull); + ASSERT_STREQ(str, " E"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} +TEST_F(LlvmLibcPrintfConverterTest, CharConversionLeftJustified) { __llvm_libc::printf_core::FormatSection left_justified_conv; left_justified_conv.has_conv = true; - left_justified_conv.raw_string = "abc123"; - left_justified_conv.raw_len = 6; + left_justified_conv.raw_string = "%-4c"; left_justified_conv.conv_name = 'c'; left_justified_conv.flags = __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; @@ -100,81 +98,77 @@ left_justified_conv.conv_val_raw = 'F'; __llvm_libc::printf_core::convert(&writer, left_justified_conv); - str[9] = '\0'; + str_writer.terminate(); - ASSERT_STREQ(str, "D EF "); - ASSERT_EQ(writer.get_chars_written(), 9ull); + ASSERT_STREQ(str, "F "); + ASSERT_EQ(writer.get_chars_written(), 4ull); } -TEST(LlvmLibcPrintfConverterTest, StringConversion) { - char str[20]; - __llvm_libc::printf_core::StringWriter str_writer(str); - __llvm_libc::printf_core::Writer writer( - reinterpret_cast(&str_writer), - __llvm_libc::printf_core::write_to_string); +TEST_F(LlvmLibcPrintfConverterTest, StringConversionSimple) { __llvm_libc::printf_core::FormatSection simple_conv; simple_conv.has_conv = true; - simple_conv.raw_string = "abc123"; - simple_conv.raw_len = 6; + simple_conv.raw_string = "%s"; simple_conv.conv_name = 's'; simple_conv.conv_val_ptr = const_cast("DEF"); __llvm_libc::printf_core::convert(&writer, simple_conv); - str[3] = '\0'; // this null terminator is just for checking after every step. + str_writer.terminate(); ASSERT_STREQ(str, "DEF"); ASSERT_EQ(writer.get_chars_written(), 3ull); +} - // continuing to write to this str_writer will overwrite that null terminator. - +TEST_F(LlvmLibcPrintfConverterTest, StringConversionPrecisionHigh) { __llvm_libc::printf_core::FormatSection high_precision_conv; high_precision_conv.has_conv = true; - high_precision_conv.raw_string = "abc123"; - high_precision_conv.raw_len = 6; + high_precision_conv.raw_string = "%4s"; high_precision_conv.conv_name = 's'; high_precision_conv.precision = 4; high_precision_conv.conv_val_ptr = const_cast("456"); __llvm_libc::printf_core::convert(&writer, high_precision_conv); - str[6] = '\0'; + str_writer.terminate(); - ASSERT_STREQ(str, "DEF456"); - ASSERT_EQ(writer.get_chars_written(), 6ull); + ASSERT_STREQ(str, "456"); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} +TEST_F(LlvmLibcPrintfConverterTest, StringConversionPrecisionLow) { __llvm_libc::printf_core::FormatSection low_precision_conv; low_precision_conv.has_conv = true; - low_precision_conv.raw_string = "abc123"; - low_precision_conv.raw_len = 6; + low_precision_conv.raw_string = "%.2s"; low_precision_conv.conv_name = 's'; low_precision_conv.precision = 2; low_precision_conv.conv_val_ptr = const_cast("xyz"); __llvm_libc::printf_core::convert(&writer, low_precision_conv); - str[8] = '\0'; + str_writer.terminate(); - ASSERT_STREQ(str, "DEF456xy"); - ASSERT_EQ(writer.get_chars_written(), 8ull); + ASSERT_STREQ(str, "xy"); + ASSERT_EQ(writer.get_chars_written(), 2ull); +} +TEST_F(LlvmLibcPrintfConverterTest, StringConversionRightJustified) { __llvm_libc::printf_core::FormatSection right_justified_conv; right_justified_conv.has_conv = true; - right_justified_conv.raw_string = "abc123"; - right_justified_conv.raw_len = 6; + right_justified_conv.raw_string = "%4s"; right_justified_conv.conv_name = 's'; right_justified_conv.min_width = 4; right_justified_conv.conv_val_ptr = const_cast("789"); __llvm_libc::printf_core::convert(&writer, right_justified_conv); - str[12] = '\0'; + str_writer.terminate(); - ASSERT_STREQ(str, "DEF456xy 789"); - ASSERT_EQ(writer.get_chars_written(), 12ull); + ASSERT_STREQ(str, " 789"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} +TEST_F(LlvmLibcPrintfConverterTest, StringConversionLeftJustified) { __llvm_libc::printf_core::FormatSection left_justified_conv; left_justified_conv.has_conv = true; - left_justified_conv.raw_string = "abc123"; - left_justified_conv.raw_len = 6; + left_justified_conv.raw_string = "%-4s"; left_justified_conv.conv_name = 's'; left_justified_conv.flags = __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; @@ -182,8 +176,450 @@ left_justified_conv.conv_val_ptr = const_cast("ghi"); __llvm_libc::printf_core::convert(&writer, left_justified_conv); - str[16] = '\0'; + str_writer.terminate(); + + ASSERT_STREQ(str, "ghi "); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionSignedPositive) { + + __llvm_libc::printf_core::FormatSection simple_conv; + simple_conv.has_conv = true; + simple_conv.raw_string = "%d"; + simple_conv.conv_name = 'd'; + simple_conv.conv_val_raw = 123; + + __llvm_libc::printf_core::convert(&writer, simple_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "123"); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionSignedNegative) { + + __llvm_libc::printf_core::FormatSection negative_conv; + negative_conv.has_conv = true; + negative_conv.raw_string = "%i"; + negative_conv.conv_name = 'i'; + negative_conv.conv_val_raw = -456; + + __llvm_libc::printf_core::convert(&writer, negative_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "-456"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} + +// Length modifier tests. + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionUnsignedChar) { + __llvm_libc::printf_core::FormatSection unsigned_char_conv; + unsigned_char_conv.has_conv = true; + unsigned_char_conv.raw_string = "%hhu"; + unsigned_char_conv.length_modifier = + __llvm_libc::printf_core::LengthModifier::hh; + unsigned_char_conv.conv_name = 'u'; + unsigned_char_conv.conv_val_raw = 257; // 0x10001 + + __llvm_libc::printf_core::convert(&writer, unsigned_char_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "1"); + ASSERT_EQ(writer.get_chars_written(), 1ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionUnsignedLongLong) { + __llvm_libc::printf_core::FormatSection unsigned_long_long_conv; + unsigned_long_long_conv.has_conv = true; + unsigned_long_long_conv.raw_string = "%llu"; + unsigned_long_long_conv.length_modifier = + __llvm_libc::printf_core::LengthModifier::ll; + unsigned_long_long_conv.conv_name = 'u'; + unsigned_long_long_conv.conv_val_raw = 18446744073709551615ull; // ull max. + + __llvm_libc::printf_core::convert(&writer, unsigned_long_long_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "18446744073709551615"); + ASSERT_EQ(writer.get_chars_written(), 20ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionSignedLongLong) { + __llvm_libc::printf_core::FormatSection unsigned_long_long_conv; + unsigned_long_long_conv.has_conv = true; + unsigned_long_long_conv.raw_string = "%lld"; + unsigned_long_long_conv.length_modifier = + __llvm_libc::printf_core::LengthModifier::ll; + unsigned_long_long_conv.conv_name = 'd'; + unsigned_long_long_conv.conv_val_raw = -9223372036854775807ll - 1ll; // ll min + // ll min is split to suppress warnings about the constant being too large for + // any signed type. + + __llvm_libc::printf_core::convert(&writer, unsigned_long_long_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "-9223372036854775808"); + ASSERT_EQ(writer.get_chars_written(), 20ull); +} - ASSERT_STREQ(str, "DEF456xy 789ghi "); - ASSERT_EQ(writer.get_chars_written(), 16ull); +// min width tests + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionMinWidthHigh) { + __llvm_libc::printf_core::FormatSection min_width_greater_conv; + min_width_greater_conv.has_conv = true; + min_width_greater_conv.raw_string = "%4d"; + min_width_greater_conv.min_width = 4; + min_width_greater_conv.conv_name = 'd'; + min_width_greater_conv.conv_val_raw = 789; + + __llvm_libc::printf_core::convert(&writer, min_width_greater_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " 789"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionMinWidthLow) { + + __llvm_libc::printf_core::FormatSection min_width_less_conv; + min_width_less_conv.has_conv = true; + min_width_less_conv.raw_string = "%2d"; + min_width_less_conv.min_width = 2; + min_width_less_conv.conv_name = 'd'; + min_width_less_conv.conv_val_raw = 987; + + __llvm_libc::printf_core::convert(&writer, min_width_less_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "987"); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +// precision tests + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionDefault) { + __llvm_libc::printf_core::FormatSection default_precision_conv; + default_precision_conv.has_conv = true; + default_precision_conv.raw_string = "%d"; + default_precision_conv.conv_name = 'd'; + default_precision_conv.conv_val_raw = 0; + + __llvm_libc::printf_core::convert(&writer, default_precision_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "0"); + ASSERT_EQ(writer.get_chars_written(), 1ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionZero) { + __llvm_libc::printf_core::FormatSection zero_precision_conv; + zero_precision_conv.has_conv = true; + zero_precision_conv.raw_string = "%d"; + zero_precision_conv.precision = 0; + zero_precision_conv.conv_name = 'd'; + zero_precision_conv.conv_val_raw = 0; + + __llvm_libc::printf_core::convert(&writer, zero_precision_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, ""); + ASSERT_EQ(writer.get_chars_written(), 0ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionHighPositive) { + __llvm_libc::printf_core::FormatSection precision_greater_conv; + precision_greater_conv.has_conv = true; + precision_greater_conv.raw_string = "%.5d"; + precision_greater_conv.precision = 5; + precision_greater_conv.conv_name = 'd'; + precision_greater_conv.conv_val_raw = 654; + + __llvm_libc::printf_core::convert(&writer, precision_greater_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "00654"); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionHighNegative) { + __llvm_libc::printf_core::FormatSection precision_greater_negative_conv; + precision_greater_negative_conv.has_conv = true; + precision_greater_negative_conv.raw_string = "%.5d"; + precision_greater_negative_conv.precision = 5; + precision_greater_negative_conv.conv_name = 'd'; + precision_greater_negative_conv.conv_val_raw = -321; + + __llvm_libc::printf_core::convert(&writer, precision_greater_negative_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "-00321"); + ASSERT_EQ(writer.get_chars_written(), 6ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionLow) { + __llvm_libc::printf_core::FormatSection precision_less_conv; + precision_less_conv.has_conv = true; + precision_less_conv.raw_string = "%.2d"; + precision_less_conv.precision = 2; + precision_less_conv.conv_name = 'd'; + precision_less_conv.conv_val_raw = 135; + + __llvm_libc::printf_core::convert(&writer, precision_less_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "135"); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +// Flag tests + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLeftJustifiedPositive) { + __llvm_libc::printf_core::FormatSection left_justified_conv; + left_justified_conv.has_conv = true; + left_justified_conv.raw_string = "%-5d"; + left_justified_conv.min_width = 5; + left_justified_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; + left_justified_conv.conv_name = 'd'; + left_justified_conv.conv_val_raw = 246; + + __llvm_libc::printf_core::convert(&writer, left_justified_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "246 "); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLeftJustifiedNegative) { + __llvm_libc::printf_core::FormatSection left_justified_conv; + left_justified_conv.has_conv = true; + left_justified_conv.raw_string = "%-5d"; + left_justified_conv.min_width = 5; + left_justified_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; + left_justified_conv.conv_name = 'd'; + left_justified_conv.conv_val_raw = -147; + + __llvm_libc::printf_core::convert(&writer, left_justified_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "-147 "); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionForceSign) { + __llvm_libc::printf_core::FormatSection force_sign_conv; + force_sign_conv.has_conv = true; + force_sign_conv.raw_string = "%+d"; + force_sign_conv.flags = __llvm_libc::printf_core::FormatFlags::FORCE_SIGN; + force_sign_conv.conv_name = 'd'; + force_sign_conv.conv_val_raw = 258; + + __llvm_libc::printf_core::convert(&writer, force_sign_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "+258"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionSpacePrefix) { + __llvm_libc::printf_core::FormatSection space_prefix_sign_conv; + space_prefix_sign_conv.has_conv = true; + space_prefix_sign_conv.raw_string = "%+d"; + space_prefix_sign_conv.flags = + __llvm_libc::printf_core::FormatFlags::SPACE_PREFIX; + space_prefix_sign_conv.conv_name = 'd'; + space_prefix_sign_conv.conv_val_raw = 369; + + __llvm_libc::printf_core::convert(&writer, space_prefix_sign_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " 369"); + ASSERT_EQ(writer.get_chars_written(), 4ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLeadingZeroesPositive) { + __llvm_libc::printf_core::FormatSection leading_zeroes_conv; + leading_zeroes_conv.has_conv = true; + leading_zeroes_conv.raw_string = "%05d"; + leading_zeroes_conv.min_width = 5; + leading_zeroes_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEADING_ZEROES; + leading_zeroes_conv.conv_name = 'd'; + leading_zeroes_conv.conv_val_raw = 470; + + __llvm_libc::printf_core::convert(&writer, leading_zeroes_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "00470"); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLeadingZeroesNegative) { + __llvm_libc::printf_core::FormatSection leading_zeroes_conv; + leading_zeroes_conv.has_conv = true; + leading_zeroes_conv.raw_string = "%05d"; + leading_zeroes_conv.min_width = 5; + leading_zeroes_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEADING_ZEROES; + leading_zeroes_conv.conv_name = 'd'; + leading_zeroes_conv.conv_val_raw = -581; + + __llvm_libc::printf_core::convert(&writer, leading_zeroes_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "-0581"); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +// combined tests + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionUnsignedWithSignedFlags) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%+ u"; + combined_conv.flags = static_cast<__llvm_libc::printf_core::FormatFlags>( + __llvm_libc::printf_core::FormatFlags::FORCE_SIGN | + __llvm_libc::printf_core::FormatFlags::SPACE_PREFIX); + combined_conv.conv_name = 'u'; + combined_conv.conv_val_raw = 692; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "692"); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionFlagPrecedence) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%+ -05d"; + combined_conv.min_width = 5; + // FORCE_SIGN takes precedence over SPACE_PREFIX + // LEFT_JUSTIFIED takes precedence over LEADING_ZEROES + combined_conv.flags = static_cast<__llvm_libc::printf_core::FormatFlags>( + __llvm_libc::printf_core::FormatFlags::FORCE_SIGN | + __llvm_libc::printf_core::FormatFlags::SPACE_PREFIX | + __llvm_libc::printf_core::FormatFlags::LEADING_ZEROES | + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED); + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = 703; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "+703 "); + ASSERT_EQ(writer.get_chars_written(), 5ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPositivePrecisionAndWidth) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%7.5d"; + combined_conv.min_width = 7; + combined_conv.precision = 5; + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = 814; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " 00814"); + ASSERT_EQ(writer.get_chars_written(), 7ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionNegativePrecisionAndWidth) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%7.5d"; + combined_conv.min_width = 7; + combined_conv.precision = 5; + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = -925; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " -00925"); + ASSERT_EQ(writer.get_chars_written(), 7ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionPrecisionOverLeadingZeroes) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%7.5d"; + combined_conv.min_width = 7; + combined_conv.precision = 5; + combined_conv.flags = __llvm_libc::printf_core::FormatFlags::LEADING_ZEROES; + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = 159; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " 00159"); + ASSERT_EQ(writer.get_chars_written(), 7ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLeftJustifiedWithOptions) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "% -7.5d"; + combined_conv.min_width = 7; + combined_conv.precision = 5; + combined_conv.flags = static_cast<__llvm_libc::printf_core::FormatFlags>( + __llvm_libc::printf_core::FormatFlags::SPACE_PREFIX | + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED); + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = 260; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, " 00260 "); + ASSERT_EQ(writer.get_chars_written(), 7ull); +} + +TEST_F(LlvmLibcPrintfConverterTest, IntConversionLowPrecisionAndWidth) { + __llvm_libc::printf_core::FormatSection combined_conv; + combined_conv.has_conv = true; + combined_conv.raw_string = "%5.4d"; + combined_conv.min_width = 5; + combined_conv.precision = 4; + combined_conv.conv_name = 'd'; + combined_conv.conv_val_raw = 10000; + + __llvm_libc::printf_core::convert(&writer, combined_conv); + + str_writer.terminate(); + + ASSERT_STREQ(str, "10000"); + ASSERT_EQ(writer.get_chars_written(), 5ull); } diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp --- a/libc/test/src/stdio/sprintf_test.cpp +++ b/libc/test/src/stdio/sprintf_test.cpp @@ -76,6 +76,30 @@ ASSERT_STREQ(buff, " beginning is important."); } +TEST(LlvmLibcSPrintfTest, IntConv) { + char buff[64]; + int written; + + // This tests all of the various integer conversion options, but for more + // thorough testing look in converter_test.cpp + written = __llvm_libc::sprintf(buff, "%d", 123); + EXPECT_EQ(written, 3); + ASSERT_STREQ(buff, "123"); + + written = __llvm_libc::sprintf(buff, "%10d %-10d", 456, -789); + EXPECT_EQ(written, 21); + ASSERT_STREQ(buff, " 456 -789 "); + + written = __llvm_libc::sprintf(buff, "%-5.4d%+.4u", 75, 25); + EXPECT_EQ(written, 9); + ASSERT_STREQ(buff, "0075 0025"); + + written = __llvm_libc::sprintf(buff, "% 05hhi %+-0.5llu %-+ 06.3zd", + 256 + 127, 68719476736ll, size_t(2)); + EXPECT_EQ(written, 24); + ASSERT_STREQ(buff, " 0127 68719476736 +002 "); +} + #ifndef LLVM_LIBC_PRINTF_DISABLE_INDEX_MODE TEST(LlvmLibcSPrintfTest, IndexModeParsing) { char buff[64];