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 @@ -37,3 +37,17 @@ DEPENDS libc.src.string.memory_utils.memset_implementation ) + +add_object_library( + converter + SRCS + converter.cpp + HDRS + converter.h + converter_atlas.h + string_converter.h + char_converter.h + DEPENDS + .writer + .core_structs +) diff --git a/libc/src/stdio/printf_core/char_converter.h b/libc/src/stdio/printf_core/char_converter.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/char_converter.h @@ -0,0 +1,31 @@ +//===-- String 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" + +namespace __llvm_libc { +namespace printf_core { +void convert_char(Writer *writer, FormatSection to_conv) { + char c = to_conv.conv_val_raw; + + if (to_conv.min_width > 1) { + if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) == + FormatFlags::LEFT_JUSTIFIED) { + writer->write(&c, 1); + writer->write_chars(' ', to_conv.min_width - 1); + } else { + writer->write_chars(' ', to_conv.min_width - 1); + writer->write(&c, 1); + } + } else { + writer->write(&c, 1); + } +} +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/src/stdio/printf_core/converter.h b/libc/src/stdio/printf_core/converter.h --- a/libc/src/stdio/printf_core/converter.h +++ b/libc/src/stdio/printf_core/converter.h @@ -21,7 +21,7 @@ Writer *writer; public: - Converter(Writer *writer); + Converter(Writer *init_writer) : writer(init_writer) {} // convert will call a conversion function to convert the FormatSection into // its string representation, and then that will write the result to the diff --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/converter.cpp @@ -0,0 +1,83 @@ +//===-- Format specifier converter implmentation 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/converter.h" + +#include "src/stdio/printf_core/core_structs.h" +#include "src/stdio/printf_core/writer.h" + +// This option allows for replacing all of the conversion functions with custom +// replacements. This allows conversions to be replaced at compile time. +#ifndef LLVM_LIBC_PRINTF_CONV_ATLAS +#include "src/stdio/printf_core/converter_atlas.h" +#else +#include LLVM_LIBC_PRINTF_CONV_ATLAS +#endif + +#include + +namespace __llvm_libc { +namespace printf_core { +void Converter::convert(FormatSection to_conv) { + if (!to_conv.has_conv) { + writer->write(to_conv.raw_string, to_conv.raw_len); + return; + } + switch (to_conv.conv_name) { + case '%': + writer->write("%", 1); + return; + case 'c': + convert_char(writer, to_conv); + return; + case 's': + convert_string(writer, to_conv); + return; + case 'd': + case 'i': + case 'u': + // convert_int(writer, to_conv); + return; + case 'o': + // convert_oct(writer, to_conv); + return; + case 'x': + case 'X': + // convert_hex(writer, to_conv); + return; + // TODO(michaelrj): add a flag to disable float point values here + case 'f': + case 'F': + // convert_float_decimal(writer, to_conv); + return; + case 'e': + case 'E': + // convert_float_dec_exp(writer, to_conv); + return; + case 'a': + case 'A': + // convert_float_hex_exp(writer, to_conv); + return; + case 'g': + case 'G': + // convert_float_mixed(writer, to_conv); + return; + // TODO(michaelrj): add a flag to disable writing an int here + case 'n': + // convert_write_int(writer, to_conv); + return; + case 'p': + // convert_pointer(writer, to_conv); + return; + default: + writer->write(to_conv.raw_string, to_conv.raw_len); + return; + } +} +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/src/stdio/printf_core/converter_atlas.h b/libc/src/stdio/printf_core/converter_atlas.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/converter_atlas.h @@ -0,0 +1,37 @@ +//===-- Map of converter headers in 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 +// +//===----------------------------------------------------------------------===// + +// This file exists so that if the user wants to supply a custom atlas they can +// just replace the #include, additionally it keeps the ifdefs out of the +// converter header. + +#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CONVERTER_ATLAS_H +#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CONVERTER_ATLAS_H + +// defines convert_string +#include "src/stdio/printf_core/string_converter.h" + +// defines convert_char +#include "src/stdio/printf_core/char_converter.h" + +// defines convert_int +// defines convert_oct +// defines convert_hex + +// TODO(michaelrj): add a flag to disable float point values here +// defines convert_float_decimal +// defines convert_float_dec_exp +// defines convert_float_hex_exp +// defines convert_float_mixed + +// TODO(michaelrj): add a flag to disable writing an int here +// defines convert_write_int + +// defines convert_pointer + +#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CONVERTER_ATLAS_H diff --git a/libc/src/stdio/printf_core/core_structs.h b/libc/src/stdio/printf_core/core_structs.h --- a/libc/src/stdio/printf_core/core_structs.h +++ b/libc/src/stdio/printf_core/core_structs.h @@ -10,6 +10,7 @@ #define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H #include "src/__support/CPP/StringView.h" +#include "src/__support/FPUtil/FPBits.h" #include #include @@ -45,7 +46,8 @@ int min_width = 0; int precision = -1; - __uint128_t conv_val_raw; // Needs to be large enough to hold a long double. + // Needs to be large enough to hold a long double. + fputil::FPBits::UIntType conv_val_raw; void *conv_val_ptr; char conv_name; diff --git a/libc/src/stdio/printf_core/parser.cpp b/libc/src/stdio/printf_core/parser.cpp --- a/libc/src/stdio/printf_core/parser.cpp +++ b/libc/src/stdio/printf_core/parser.cpp @@ -13,6 +13,7 @@ #include "src/__support/arg_list.h" #include "src/__support/CPP/Bit.h" +#include "src/__support/FPUtil/FPBits.h" #include "src/__support/ctype_utils.h" #include "src/__support/str_to_integer.h" @@ -120,6 +121,7 @@ break; } break; + // TODO(michaelrj): add a flag to disable float point values here case ('f'): case ('F'): case ('e'): @@ -132,7 +134,7 @@ section.conv_val_raw = bit_cast(GET_ARG_VAL_SIMPLEST(double, conv_index)); else - section.conv_val_raw = bit_cast<__uint128_t>( + section.conv_val_raw = bit_cast::UIntType>( GET_ARG_VAL_SIMPLEST(long double, conv_index)); break; case ('n'): @@ -335,6 +337,7 @@ break; } break; + // TODO(michaelrj): add a flag to disable float point values here case ('f'): case ('F'): case ('e'): @@ -388,6 +391,7 @@ args_cur.next_var(); else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); + // TODO(michaelrj): add a flag to disable float point values here // Floating point numbers are stored separately from the other arguments. else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); diff --git a/libc/src/stdio/printf_core/string_converter.h b/libc/src/stdio/printf_core/string_converter.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/string_converter.h @@ -0,0 +1,44 @@ +//===-- String 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 + +namespace __llvm_libc { +namespace printf_core { +void convert_string(Writer *writer, FormatSection to_conv) { + int string_len = 0; + + for (char *cur_str = reinterpret_cast(to_conv.conv_val_ptr); + cur_str[string_len]; ++string_len) { + ; + } + + if (to_conv.precision >= 0 && to_conv.precision < string_len) + string_len = to_conv.precision; + + if (to_conv.min_width > string_len) { + if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) == + FormatFlags::LEFT_JUSTIFIED) { + writer->write(reinterpret_cast(to_conv.conv_val_ptr), + string_len); + writer->write_chars(' ', to_conv.min_width - string_len); + } else { + writer->write_chars(' ', to_conv.min_width - string_len); + writer->write(reinterpret_cast(to_conv.conv_val_ptr), + string_len); + } + } else { + writer->write(reinterpret_cast(to_conv.conv_val_ptr), + string_len); + } +} +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/printf_core/CMakeLists.txt b/libc/test/src/stdio/printf_core/CMakeLists.txt --- a/libc/test/src/stdio/printf_core/CMakeLists.txt +++ b/libc/test/src/stdio/printf_core/CMakeLists.txt @@ -6,6 +6,7 @@ parser_test.cpp DEPENDS libc.src.stdio.printf_core.parser + libc.src.stdio.printf_core.core_structs libc.src.__support.arg_list LINK_LIBRARIES LibcPrintfHelpers @@ -21,3 +22,16 @@ libc.src.stdio.printf_core.writer libc.src.stdio.printf_core.string_writer ) + +add_libc_unittest( + converter_test + SUITE + libc_stdio_unittests + SRCS + converter_test.cpp + DEPENDS + libc.src.stdio.printf_core.converter + libc.src.stdio.printf_core.writer + libc.src.stdio.printf_core.string_writer + libc.src.stdio.printf_core.core_structs +) diff --git a/libc/test/src/stdio/printf_core/converter_test.cpp b/libc/test/src/stdio/printf_core/converter_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/printf_core/converter_test.cpp @@ -0,0 +1,202 @@ +//===-- Unittests for the printf Converter --------------------------------===// +// +// 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/converter.h" +#include "src/stdio/printf_core/core_structs.h" +#include "src/stdio/printf_core/string_writer.h" +#include "src/stdio/printf_core/writer.h" + +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcPrintfConverterTest, Constructor) { + char str[10]; + __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); + __llvm_libc::printf_core::Converter converter(&writer); +} + +TEST(LlvmLibcPrintfConverterTest, SimpleRawConversion) { + char str[10]; + __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); + __llvm_libc::printf_core::Converter converter(&writer); + + __llvm_libc::printf_core::FormatSection raw_section; + raw_section.has_conv = false; + raw_section.raw_string = "abc"; + raw_section.raw_len = 3; + + converter.convert(raw_section); + + str_writer.terminate(); + + ASSERT_STREQ(str, "abc"); + 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); + __llvm_libc::printf_core::Converter converter(&writer); + + __llvm_libc::printf_core::FormatSection simple_conv; + simple_conv.has_conv = true; + simple_conv.raw_string = "abc123"; + simple_conv.raw_len = 6; + simple_conv.conv_name = '%'; + + converter.convert(simple_conv); + + str[1] = '\0'; + + ASSERT_STREQ(str, "%"); + 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); + __llvm_libc::printf_core::Converter converter(&writer); + + __llvm_libc::printf_core::FormatSection simple_conv; + simple_conv.has_conv = true; + simple_conv.raw_string = "abc123"; + simple_conv.raw_len = 6; + simple_conv.conv_name = 'c'; + simple_conv.conv_val_raw = 'D'; + + converter.convert(simple_conv); + + str[1] = '\0'; + + ASSERT_STREQ(str, "D"); + ASSERT_EQ(writer.get_chars_written(), 1ull); + + __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.conv_name = 'c'; + right_justified_conv.min_width = 4; + right_justified_conv.conv_val_raw = 'E'; + converter.convert(right_justified_conv); + + str[5] = '\0'; + + ASSERT_STREQ(str, "D E"); + ASSERT_EQ(writer.get_chars_written(), 5ull); + + __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.conv_name = 'c'; + left_justified_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; + left_justified_conv.min_width = 4; + left_justified_conv.conv_val_raw = 'F'; + converter.convert(left_justified_conv); + + str[9] = '\0'; + + ASSERT_STREQ(str, "D EF "); + ASSERT_EQ(writer.get_chars_written(), 9ull); +} + +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); + __llvm_libc::printf_core::Converter converter(&writer); + + __llvm_libc::printf_core::FormatSection simple_conv; + simple_conv.has_conv = true; + simple_conv.raw_string = "abc123"; + simple_conv.raw_len = 6; + simple_conv.conv_name = 's'; + simple_conv.conv_val_ptr = const_cast("DEF"); + + converter.convert(simple_conv); + + str[3] = '\0'; // this null terminator is just for checking after every step. + + ASSERT_STREQ(str, "DEF"); + ASSERT_EQ(writer.get_chars_written(), 3ull); + + // continuing to write to this str_writer will overwrite that null terminator. + + __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.conv_name = 's'; + high_precision_conv.precision = 4; + high_precision_conv.conv_val_ptr = const_cast("456"); + converter.convert(high_precision_conv); + + str[6] = '\0'; + + ASSERT_STREQ(str, "DEF456"); + ASSERT_EQ(writer.get_chars_written(), 6ull); + + __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.conv_name = 's'; + low_precision_conv.precision = 2; + low_precision_conv.conv_val_ptr = const_cast("xyz"); + converter.convert(low_precision_conv); + + str[8] = '\0'; + + ASSERT_STREQ(str, "DEF456xy"); + ASSERT_EQ(writer.get_chars_written(), 8ull); + + __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.conv_name = 's'; + right_justified_conv.min_width = 4; + right_justified_conv.conv_val_ptr = const_cast("789"); + converter.convert(right_justified_conv); + + str[12] = '\0'; + + ASSERT_STREQ(str, "DEF456xy 789"); + ASSERT_EQ(writer.get_chars_written(), 12ull); + + __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.conv_name = 's'; + left_justified_conv.flags = + __llvm_libc::printf_core::FormatFlags::LEFT_JUSTIFIED; + left_justified_conv.min_width = 4; + left_justified_conv.conv_val_ptr = const_cast("ghi"); + converter.convert(left_justified_conv); + + str[16] = '\0'; + + ASSERT_STREQ(str, "DEF456xy 789ghi "); + ASSERT_EQ(writer.get_chars_written(), 16ull); +}