diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt --- a/libc/src/__support/CPP/CMakeLists.txt +++ b/libc/src/__support/CPP/CMakeLists.txt @@ -58,6 +58,16 @@ StringView.h ) +add_header_library( + stringstream + HDRS + stringstream.h + DEPENDS + .array_ref + .string_view + libc.src.__support.integer_to_string +) + add_header_library( type_traits HDRS diff --git a/libc/src/__support/CPP/stringstream.h b/libc/src/__support/CPP/stringstream.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/CPP/stringstream.h @@ -0,0 +1,93 @@ +//===-- A simple implementation of string stream class ----------*- 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_SUPPORT_CPP_STRINGSTREAM_H +#define LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H + +#include "ArrayRef.h" +#include "StringView.h" +#include "TypeTraits.h" + +#include "src/__support/integer_to_string.h" + +namespace __llvm_libc { +namespace cpp { + +// This class is to be used to write simple strings into a user provided buffer +// without any dynamic memory allocation. There is no requirement to mimic the +// C++ standard library class std::stringstream. +class StringStream { + MutableArrayRef data; + size_t write_ptr = 0; // The current write pointer + bool err = false; // If an error occurs while writing + + void write(const char *bytes, size_t size) { + size_t i = 0; + const size_t data_size = data.size(); + for (; write_ptr < data_size && i < size; ++i, ++write_ptr) + data[write_ptr] = bytes[i]; + if (i < size) { + // If some of the characters couldn't be written, set error. + err = true; + } + } + +public: + static constexpr char ENDS = '\0'; + + // Create a string stream which will write into |buf|. + constexpr StringStream(const MutableArrayRef &buf) : data(buf) {} + + // Return a StringView to the current characters in the stream. If a + // null terminator was not explicitly written, then the return value + // will not include one. In order to produce a StringView to a null + // terminated string, write ENDS explicitly. + StringView str() const { return StringView(data.data(), write_ptr); } + + // Write the characters from |str| to the stream. + StringStream &operator<<(StringView str) { + write(str.data(), str.size()); + return *this; + } + + // Write the |val| as string. + template ::Value, int> = 0> + StringStream &operator<<(T val) { + const auto int_to_str = integer_to_string(val); + return operator<<(int_to_str.str()); + } + + template ::Value, int> = 0> + StringStream &operator<<(T val) { + // If this specialization gets activated, then the static_assert will + // trigger a compile error about missing floating point number support. + static_assert(!IsFloatingPointType::Value, + "Writing floating point numbers is not yet supported"); + return *this; + } + + // Write a null-terminated string. The terminating null character is not + // written to allow stremaing to continue. + StringStream &operator<<(const char *str) { + return operator<<(StringView(str)); + } + + // Write a single character. + StringStream &operator<<(char a) { + write(&a, 1); + return *this; + } + + // Return true if any write operation(s) failed due to insufficient size. + bool overflow() const { return err; } +}; + +} // namespace cpp +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt --- a/libc/test/src/__support/CPP/CMakeLists.txt +++ b/libc/test/src/__support/CPP/CMakeLists.txt @@ -80,3 +80,14 @@ DEPENDS libc.src.__support.CPP.blockstore ) + +add_libc_unittest( + stringstream_test + SUITE + libc_cpp_utils_unittests + SRCS + stringstream_test.cpp + DEPENDS + libc.src.__support.CPP.array_ref + libc.src.__support.CPP.stringstream +) diff --git a/libc/test/src/__support/CPP/stringstream_test.cpp b/libc/test/src/__support/CPP/stringstream_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/__support/CPP/stringstream_test.cpp @@ -0,0 +1,51 @@ +//===-- Unittests for StringStream ----------------------------------------===// +// +// 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/__support/CPP/ArrayRef.h" +#include "src/__support/CPP/stringstream.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::cpp::MutableArrayRef; +using __llvm_libc::cpp::StringStream; + +TEST(LlvmLibcStringStreamTest, Simple) { + char buf[256]; + MutableArrayRef bufref(reinterpret_cast(buf), 256); + + StringStream ss1(bufref); + ss1 << "Hello, Stream - " << int(123) << StringStream::ENDS; + ASSERT_FALSE(ss1.overflow()); + ASSERT_STREQ(ss1.str().data(), "Hello, Stream - 123"); + + StringStream ss2(bufref); + ss2 << 'a' << 'b' << 'c' << StringStream::ENDS; + ASSERT_FALSE(ss2.overflow()); + ASSERT_STREQ(ss2.str().data(), "abc"); +} + +TEST(LlvmLibcStringStreamTest, Overflow) { + constexpr size_t BUFSIZE = 8; + char buf[BUFSIZE]; + MutableArrayRef bufref(reinterpret_cast(buf), BUFSIZE); + + StringStream ss1(bufref); + ss1 << "Hello, Stream - " << int(123) << StringStream::ENDS; + ASSERT_TRUE(ss1.overflow()); + ASSERT_EQ(ss1.str().size(), BUFSIZE); + + StringStream ss2(bufref); + ss2 << "7777777"; + ASSERT_FALSE(ss2.overflow()); + ASSERT_EQ(ss2.str().size(), size_t(7)); + ss2 << "8"; + ASSERT_FALSE(ss2.overflow()); + ASSERT_EQ(ss2.str().size(), size_t(8)); + ss2 << StringStream::ENDS; + ASSERT_TRUE(ss2.overflow()); + ASSERT_EQ(ss2.str().size(), BUFSIZE); +}