diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -268,6 +268,7 @@ libc.src.stdio.funlockfile libc.src.stdio.fwrite libc.src.stdio.fwrite_unlocked + libc.src.stdio.sprintf # signal.h entrypoints # TODO: Enable signal.h entrypoints after fixing signal.h diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -532,6 +532,13 @@ ArgSpec, ArgSpec] >, + FunctionSpec< + "sprintf", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, ] >; diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -202,3 +202,17 @@ libc.include.stdio libc.src.__support.File.file ) + + +add_entrypoint_object( + sprintf + SRCS + sprintf.cpp + HDRS + sprintf.h + DEPENDS + libc.include.stdio + libc.src.stdio.printf_core.printf_main + libc.src.stdio.printf_core.string_writer + libc.src.stdio.printf_core.writer +) 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 @@ -51,3 +51,16 @@ .writer .core_structs ) + + +add_header_library( + printf_main + HDRS + printf_main.h + DEPENDS + .parser + .converter + .writer + .core_structs + libc.src.__support.arg_list +) diff --git a/libc/src/stdio/printf_core/char_converter.h b/libc/src/stdio/printf_core/char_converter.h --- a/libc/src/stdio/printf_core/char_converter.h +++ b/libc/src/stdio/printf_core/char_converter.h @@ -12,7 +12,7 @@ namespace __llvm_libc { namespace printf_core { -void convert_char(Writer *writer, FormatSection to_conv) { +void convert_char(Writer *writer, const FormatSection &to_conv) { char c = to_conv.conv_val_raw; if (to_conv.min_width > 1) { 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 @@ -20,7 +20,7 @@ // convert will call a conversion function to convert the FormatSection into // its string representation, and then that will write the result to the // writer. -void convert(Writer *writer, FormatSection to_conv); +void convert(Writer *writer, const FormatSection &to_conv); } // namespace printf_core } // namespace __llvm_libc 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 @@ -24,7 +24,7 @@ namespace __llvm_libc { namespace printf_core { -void convert(Writer *writer, FormatSection to_conv) { +void convert(Writer *writer, const FormatSection &to_conv) { if (!to_conv.has_conv) { writer->write(to_conv.raw_string, to_conv.raw_len); return; diff --git a/libc/src/stdio/printf_core/printf_main.h b/libc/src/stdio/printf_core/printf_main.h --- a/libc/src/stdio/printf_core/printf_main.h +++ b/libc/src/stdio/printf_core/printf_main.h @@ -21,7 +21,7 @@ namespace printf_core { int printf_main(Writer *writer, const char *__restrict str, - internal::ArgList args) { + internal::ArgList &args) { Parser parser(str, args); for (FormatSection cur_section = parser.get_next_section(); diff --git a/libc/src/stdio/printf_core/string_converter.h b/libc/src/stdio/printf_core/string_converter.h --- a/libc/src/stdio/printf_core/string_converter.h +++ b/libc/src/stdio/printf_core/string_converter.h @@ -14,7 +14,7 @@ namespace __llvm_libc { namespace printf_core { -void convert_string(Writer *writer, FormatSection to_conv) { +void convert_string(Writer *writer, const FormatSection &to_conv) { int string_len = 0; for (char *cur_str = reinterpret_cast(to_conv.conv_val_ptr); diff --git a/libc/src/stdio/sprintf.h b/libc/src/stdio/sprintf.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/sprintf.h @@ -0,0 +1,18 @@ +//===-- Implementation header of sprintf ------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_SPRINTF_H +#define LLVM_LIBC_SRC_STDIO_SPRINTF_H + +namespace __llvm_libc { + +int sprintf(char *__restrict buffer, const char *__restrict format, ...); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_SPRINTF_H diff --git a/libc/src/stdio/sprintf.cpp b/libc/src/stdio/sprintf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/sprintf.cpp @@ -0,0 +1,38 @@ +//===-- Implementation of sprintf -------------------------------*- 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/sprintf.h" + +#include "src/__support/arg_list.h" +#include "src/stdio/printf_core/printf_main.h" +#include "src/stdio/printf_core/string_writer.h" +#include "src/stdio/printf_core/writer.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, sprintf, + (char *__restrict buffer, const char *__restrict format, + ...)) { + va_list vlist; + va_start(vlist, format); + internal::ArgList args(vlist); // This holder class allows for easier copying + // and pointer semantics, as well as handing + // destruction automatically. + va_end(vlist); + printf_core::StringWriter str_writer(buffer); + printf_core::Writer writer(reinterpret_cast(&str_writer), + printf_core::write_to_string); + + int ret_val = printf_core::printf_main(&writer, format, args); + str_writer.terminate(); + return ret_val; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -63,6 +63,16 @@ LibcMemoryHelpers ) +add_libc_unittest( + sprintf_test + SUITE + libc_stdio_unittests + SRCS + sprintf_test.cpp + DEPENDS + libc.src.stdio.sprintf +) + add_subdirectory(printf_core) add_subdirectory(testdata) diff --git a/libc/test/src/stdio/printf_core/parser_test.cpp b/libc/test/src/stdio/printf_core/parser_test.cpp --- a/libc/test/src/stdio/printf_core/parser_test.cpp +++ b/libc/test/src/stdio/printf_core/parser_test.cpp @@ -53,6 +53,7 @@ expected.raw_string = str; ASSERT_FORMAT_EQ(expected, format_arr[0]); + // TODO: add checks that the format_arr after the last one has length 0 } TEST(LlvmLibcPrintfParserTest, EvalSimple) { diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/sprintf_test.cpp @@ -0,0 +1,104 @@ +//===-- Unittests for sprintf ---------------------------------------------===// +// +// 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/sprintf.h" + +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcSPrintfTest, SimpleNoConv) { + char buff[64]; + int written; + + written = __llvm_libc::sprintf(buff, "A simple string with no conversions."); + EXPECT_EQ(written, 36); + ASSERT_STREQ(buff, "A simple string with no conversions."); +} + +TEST(LlvmLibcSPrintfTest, PercentConv) { + char buff[64]; + int written; + + written = __llvm_libc::sprintf(buff, "%%"); + EXPECT_EQ(written, 1); + ASSERT_STREQ(buff, "%"); + + written = __llvm_libc::sprintf(buff, "abc %% def"); + EXPECT_EQ(written, 9); + ASSERT_STREQ(buff, "abc % def"); + + written = __llvm_libc::sprintf(buff, "%%%%%%"); + EXPECT_EQ(written, 3); + ASSERT_STREQ(buff, "%%%"); +} + +TEST(LlvmLibcSPrintfTest, CharConv) { + char buff[64]; + int written; + + written = __llvm_libc::sprintf(buff, "%c", 'a'); + EXPECT_EQ(written, 1); + ASSERT_STREQ(buff, "a"); + + written = __llvm_libc::sprintf(buff, "%3c %-3c", '1', '2'); + EXPECT_EQ(written, 7); + ASSERT_STREQ(buff, " 1 2 "); + + written = __llvm_libc::sprintf(buff, "%*c", 2, '3'); + EXPECT_EQ(written, 2); + ASSERT_STREQ(buff, " 3"); +} + +TEST(LlvmLibcSPrintfTest, StringConv) { + char buff[64]; + int written; + + written = __llvm_libc::sprintf(buff, "%s", "abcDEF123"); + EXPECT_EQ(written, 9); + ASSERT_STREQ(buff, "abcDEF123"); + + written = __llvm_libc::sprintf(buff, "%10s %-10s", "centered", "title"); + EXPECT_EQ(written, 21); + ASSERT_STREQ(buff, " centered title "); + + written = __llvm_libc::sprintf(buff, "%-5.4s%-4.4s", "words can describe", + "soups most delicious"); + EXPECT_EQ(written, 9); + ASSERT_STREQ(buff, "word soup"); + + written = __llvm_libc::sprintf(buff, "%*s %.*s %*.*s", 10, "beginning", 2, + "isn't", 12, 10, "important. Ever."); + EXPECT_EQ(written, 26); + ASSERT_STREQ(buff, " beginning is important."); +} + +#ifndef LLVM_LIBC_PRINTF_DISABLE_INDEX_MODE +TEST(LlvmLibcSPrintfTest, IndexModeParsing) { + char buff[64]; + int written; + + written = __llvm_libc::sprintf(buff, "%1$s", "abcDEF123"); + EXPECT_EQ(written, 9); + ASSERT_STREQ(buff, "abcDEF123"); + + written = __llvm_libc::sprintf(buff, "%1$s %%", "abcDEF123"); + EXPECT_EQ(written, 11); + ASSERT_STREQ(buff, "abcDEF123 %"); + + written = + __llvm_libc::sprintf(buff, "%3$s %1$s %2$s", "is", "hard", "ordering"); + EXPECT_EQ(written, 16); + ASSERT_STREQ(buff, "ordering is hard"); + + written = __llvm_libc::sprintf( + buff, "%10$s %9$s %8$c %7$s %6$s, %6$s %5$s %4$-*1$s %3$.*11$s %2$s. %%", + 6, "pain", "alphabetical", "such", "is", "this", "do", 'u', "would", + "why", 1); + EXPECT_EQ(written, 45); + ASSERT_STREQ(buff, "why would u do this, this is such a pain. %"); +} +#endif // LLVM_LIBC_PRINTF_DISABLE_INDEX_MODE