| // Copyright 2019 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| #pragma once |
| /// @file pw_string/string_builder.h |
| /// |
| /// @brief `pw::StringBuilder` facilitates creating formatted strings in a |
| /// fixed-sized buffer or in a `pw::InlineString`. It is designed to give the |
| /// flexibility of std::ostringstream, but with a small footprint. |
| |
| #include <algorithm> |
| #include <cstdarg> |
| #include <cstddef> |
| #include <cstring> |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "pw_preprocessor/compiler.h" |
| #include "pw_span/span.h" |
| #include "pw_status/status.h" |
| #include "pw_status/status_with_size.h" |
| #include "pw_string/string.h" |
| #include "pw_string/to_string.h" |
| |
| namespace pw { |
| |
| /// @class StringBuilder |
| /// |
| /// `pw::StringBuilder` instances are always null-terminated (unless they are |
| /// constructed with an empty buffer) and never overflow. Status is tracked for |
| /// each operation and an overall status is maintained, which reflects the most |
| /// recent error. |
| /// |
| /// `pw::StringBuilder` does not own the buffer it writes to. It can be used |
| /// to write strings to any buffer. The `pw::StringBuffer` template class, |
| /// defined below, allocates a buffer alongside a `pw::StringBuilder`. |
| /// |
| /// `pw::StringBuilder` supports C++-style `<<` output, similar to |
| /// `std::ostringstream`. It also supports append functions like `std::string` |
| /// and `printf`-style output. |
| /// |
| /// Support for custom types is added by overloading `operator<<` in the same |
| /// namespace as the custom type. For example: |
| /// |
| /// @code |
| /// namespace my_project { |
| /// |
| /// struct MyType { |
| /// int foo; |
| /// const char* bar; |
| /// }; |
| /// |
| /// pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) |
| /// { |
| /// return sb << "MyType(" << value.foo << ", " << value.bar << ')'; |
| /// } |
| /// |
| /// } // namespace my_project |
| /// @endcode |
| /// |
| /// The `ToString` template function can be specialized to support custom types |
| /// with `pw::StringBuilder`, though overloading `operator<<` is generally |
| /// preferred. For example: |
| /// |
| /// @code |
| /// namespace pw { |
| /// |
| /// template <> |
| /// StatusWithSize ToString<MyStatus>(MyStatus value, span<char> buffer) { |
| /// return Copy(MyStatusString(value), buffer); |
| /// } |
| /// |
| /// } // namespace pw |
| /// @endcode |
| /// |
| class StringBuilder { |
| public: |
| /// Creates an empty `pw::StringBuilder`. |
| explicit constexpr StringBuilder(span<char> buffer) |
| : buffer_(buffer), size_(&inline_size_), inline_size_(0) { |
| NullTerminate(); |
| } |
| |
| explicit StringBuilder(span<std::byte> buffer) |
| : StringBuilder( |
| {reinterpret_cast<char*>(buffer.data()), buffer.size_bytes()}) {} |
| |
| explicit constexpr StringBuilder(InlineString<>& string) |
| : buffer_(string.data(), string.max_size() + 1), |
| size_(&string.length_), |
| inline_size_(0) {} |
| |
| /// Disallow copy/assign to avoid confusion about where the string is actually |
| /// stored. `pw::StringBuffer` instances may be copied into one another. |
| StringBuilder(const StringBuilder&) = delete; |
| |
| StringBuilder& operator=(const StringBuilder&) = delete; |
| |
| /// @fn data |
| /// @fn c_str |
| /// |
| /// Returns the contents of the string buffer. Always null-terminated. |
| const char* data() const { return buffer_.data(); } |
| const char* c_str() const { return data(); } |
| |
| /// Returns a `std::string_view` of the contents of this `pw::StringBuilder`. |
| /// The `std::string_view` is invalidated if the `pw::StringBuilder` contents |
| /// change. |
| std::string_view view() const { return std::string_view(data(), size()); } |
| |
| /// Allow implicit conversions to `std::string_view` so `pw::StringBuilder` |
| /// instances can be passed into functions that take a `std::string_view`. |
| operator std::string_view() const { return view(); } |
| |
| /// Returns a `span<const std::byte>` representation of this |
| /// `pw::StringBuffer`. |
| span<const std::byte> as_bytes() const { |
| return span(reinterpret_cast<const std::byte*>(buffer_.data()), size()); |
| } |
| |
| /// Returns the status of `pw::StringBuilder`, which reflects the most recent |
| /// error that occurred while updating the string. After an update fails, the |
| /// status remains non-OK until it is cleared with |
| /// `pw::StringBuilder::clear()` or `pw::StringBuilder::clear_status()`. |
| /// |
| /// @returns `OK` if no errors have occurred; `RESOURCE_EXHAUSTED` if output |
| /// to the `StringBuilder` was truncated; `INVALID_ARGUMENT` if `printf`-style |
| /// formatting failed; `OUT_OF_RANGE` if an operation outside the buffer was |
| /// attempted. |
| Status status() const { return static_cast<Status::Code>(status_); } |
| |
| /// Returns `status()` and `size()` as a `StatusWithSize`. |
| StatusWithSize status_with_size() const { |
| return StatusWithSize(status(), size()); |
| } |
| |
| /// The status from the last operation. May be OK while `status()` is not OK. |
| Status last_status() const { return static_cast<Status::Code>(last_status_); } |
| |
| /// True if `status()` is `OkStatus()`. |
| bool ok() const { return status().ok(); } |
| |
| /// True if the string is empty. |
| bool empty() const { return size() == 0u; } |
| |
| /// Returns the current length of the string, excluding the null terminator. |
| size_t size() const { return *size_; } |
| |
| /// Returns the maximum length of the string, excluding the null terminator. |
| size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; } |
| |
| /// Clears the string and resets its error state. |
| void clear(); |
| |
| /// Sets the statuses to `OkStatus()`; |
| void clear_status() { |
| status_ = static_cast<unsigned char>(OkStatus().code()); |
| last_status_ = static_cast<unsigned char>(OkStatus().code()); |
| } |
| |
| /// Appends a single character. Sets the status to `RESOURCE_EXHAUSTED` if the |
| /// character cannot be added because the buffer is full. |
| void push_back(char ch) { append(1, ch); } |
| |
| /// Removes the last character. Sets the status to `OUT_OF_RANGE` if the |
| /// buffer is empty (in which case the unsigned overflow is intentional). |
| void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") { |
| resize(size() - 1); |
| } |
| |
| /// Appends the provided character `count` times. |
| StringBuilder& append(size_t count, char ch); |
| |
| /// Appends `count` characters from `str` to the end of the `StringBuilder`. |
| /// If count exceeds the remaining space in the `StringBuffer`, |
| /// `max_size() - size()` characters are appended and the status is set to |
| /// `RESOURCE_EXHAUSTED`. |
| /// |
| /// `str` is not considered null-terminated and may contain null characters. |
| StringBuilder& append(const char* str, size_t count); |
| |
| /// Appends characters from the null-terminated string to the end of the |
| /// `StringBuilder`. If the string's length exceeds the remaining space in the |
| /// buffer, `max_size() - size()` characters are copied and the status is |
| /// set to `RESOURCE_EXHAUSTED`. |
| /// |
| /// This function uses `string::Length` instead of `std::strlen` to avoid |
| /// unbounded reads if the string is not null-terminated. |
| StringBuilder& append(const char* str); |
| |
| /// Appends a `std::string_view` to the end of the `StringBuilder`. |
| StringBuilder& append(const std::string_view& str); |
| |
| /// Appends a substring from the `std::string_view` to the `StringBuilder`. |
| /// Copies up to count characters starting from `pos` to the end of the |
| /// `StringBuilder`. If `pos > str.size()`, sets the status to `OUT_OF_RANGE`. |
| StringBuilder& append(const std::string_view& str, |
| size_t pos, |
| size_t count = std::string_view::npos); |
| |
| /// Appends to the end of the `StringBuilder` using the `<<` operator. This |
| /// enables C++ stream-style formatted to `StringBuilder` instances. |
| template <typename T> |
| StringBuilder& operator<<(const T& value) { |
| /// For types compatible with `std::string_view`, use the `append` function, |
| /// which gives smaller code size. |
| if constexpr (std::is_convertible_v<T, std::string_view>) { |
| append(value); |
| } else if constexpr (std::is_convertible_v<T, span<const std::byte>>) { |
| WriteBytes(value); |
| } else { |
| HandleStatusWithSize(ToString(value, buffer_.subspan(size()))); |
| } |
| return *this; |
| } |
| |
| /// Provide a few additional `operator<<` overloads that reduce code size. |
| StringBuilder& operator<<(bool value) { |
| return append(value ? "true" : "false"); |
| } |
| |
| StringBuilder& operator<<(char value) { |
| push_back(value); |
| return *this; |
| } |
| |
| StringBuilder& operator<<(std::nullptr_t) { |
| return append(string::kNullPointerString); |
| } |
| |
| StringBuilder& operator<<(Status status) { return *this << status.str(); } |
| |
| /// @fn pw::StringBuilder::Format |
| /// Appends a `printf`-style string to the end of the `StringBuilder`. If the |
| /// formatted string does not fit, the results are truncated and the status is |
| /// set to `RESOURCE_EXHAUSTED`. |
| /// |
| /// @param format The format string |
| /// @param ... Arguments for format specification |
| /// |
| /// @returns `StringBuilder&` |
| /// |
| /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`. |
| PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...); |
| |
| /// Appends a `vsnprintf`-style string with `va_list` arguments to the end of |
| /// the `StringBuilder`. If the formatted string does not fit, the results are |
| /// truncated and the status is set to `RESOURCE_EXHAUSTED`. |
| /// |
| /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`. |
| PW_PRINTF_FORMAT(2, 0) |
| StringBuilder& FormatVaList(const char* format, va_list args); |
| |
| /// Sets the size of the `StringBuilder`. This function only truncates; if |
| /// `new_size > size()`, it sets status to `OUT_OF_RANGE` and does nothing. |
| void resize(size_t new_size); |
| |
| protected: |
| /// Functions to support `StringBuffer` copies. |
| constexpr StringBuilder(span<char> buffer, const StringBuilder& other) |
| : buffer_(buffer), |
| size_(&inline_size_), |
| inline_size_(*other.size_), |
| status_(other.status_), |
| last_status_(other.last_status_) {} |
| |
| void CopySizeAndStatus(const StringBuilder& other); |
| |
| private: |
| /// Statuses are stored as an `unsigned char` so they pack into a single word. |
| static constexpr unsigned char StatusCode(Status status) { |
| return static_cast<unsigned char>(status.code()); |
| } |
| |
| void WriteBytes(span<const std::byte> data); |
| |
| size_t ResizeAndTerminate(size_t chars_to_append); |
| |
| void HandleStatusWithSize(StatusWithSize written); |
| |
| constexpr void NullTerminate() { |
| if (!buffer_.empty()) { |
| buffer_[size()] = '\0'; |
| } |
| } |
| |
| void SetErrorStatus(Status status); |
| |
| const span<char> buffer_; |
| |
| InlineString<>::size_type* size_; |
| |
| // Place the `inline_size_`, `status_`, and `last_status_` members together |
| // and use `unsigned char` for the status codes so these members can be |
| // packed into a single word. |
| InlineString<>::size_type inline_size_; |
| unsigned char status_ = StatusCode(OkStatus()); |
| unsigned char last_status_ = StatusCode(OkStatus()); |
| }; |
| |
| // StringBuffer declares a buffer along with a StringBuilder. StringBuffer |
| // can be used as a statically allocated replacement for std::ostringstream or |
| // std::string. For example: |
| // |
| // StringBuffer<32> str; |
| // str << "The answer is " << number << "!"; // with number = 42 |
| // str.c_str(); // null terminated C string "The answer is 42." |
| // str.view(); // std::string_view of "The answer is 42." |
| // |
| template <size_t kSizeBytes> |
| class StringBuffer : public StringBuilder { |
| public: |
| StringBuffer() : StringBuilder(buffer_) {} |
| |
| // StringBuffers of the same size may be copied and assigned into one another. |
| StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) { |
| CopyContents(other); |
| } |
| |
| // A smaller StringBuffer may be copied or assigned into a larger one. |
| template <size_t kOtherSizeBytes> |
| StringBuffer(const StringBuffer<kOtherSizeBytes>& other) |
| : StringBuilder(buffer_, other) { |
| static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(), |
| "A StringBuffer cannot be copied into a smaller buffer"); |
| CopyContents(other); |
| } |
| |
| template <size_t kOtherSizeBytes> |
| StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) { |
| assign<kOtherSizeBytes>(other); |
| return *this; |
| } |
| |
| StringBuffer& operator=(const StringBuffer& other) { |
| assign<kSizeBytes>(other); |
| return *this; |
| } |
| |
| template <size_t kOtherSizeBytes> |
| StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) { |
| static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(), |
| "A StringBuffer cannot be copied into a smaller buffer"); |
| CopySizeAndStatus(other); |
| CopyContents(other); |
| return *this; |
| } |
| |
| // Returns the maximum length of the string, excluding the null terminator. |
| static constexpr size_t max_size() { return kSizeBytes - 1; } |
| |
| // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for |
| // append calls and stream-style operations. |
| template <typename... Args> |
| StringBuffer& append(Args&&... args) { |
| StringBuilder::append(std::forward<Args>(args)...); |
| return *this; |
| } |
| |
| template <typename T> |
| StringBuffer& operator<<(T&& value) { |
| static_cast<StringBuilder&>(*this) << std::forward<T>(value); |
| return *this; |
| } |
| |
| private: |
| template <size_t kOtherSize> |
| void CopyContents(const StringBuffer<kOtherSize>& other) { |
| std::memcpy(buffer_, other.data(), other.size() + 1); // include the \0 |
| } |
| |
| static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long"); |
| char buffer_[kSizeBytes]; |
| }; |
| |
| namespace string_internal { |
| |
| // Internal code for determining the default size of StringBuffers created with |
| // MakeString. |
| // |
| // StringBuffers created with MakeString default to at least 24 bytes. This is |
| // large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded |
| // up to the nearest multiple of 4. |
| inline constexpr size_t kDefaultMinimumStringBufferSize = 24; |
| |
| // By default, MakeString uses a buffer size large enough to fit all string |
| // literal arguments. ArgLength uses this value as an estimate of the number of |
| // characters needed to represent a non-string argument. |
| inline constexpr size_t kDefaultArgumentSize = 4; |
| |
| // Returns a string literal's length or kDefaultArgumentSize for non-strings. |
| template <typename T> |
| constexpr size_t ArgLength() { |
| using Arg = std::remove_reference_t<T>; |
| |
| // If the argument is an array of const char, assume it is a string literal. |
| if constexpr (std::is_array_v<Arg>) { |
| using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>; |
| |
| if constexpr (std::is_same_v<Element, const char>) { |
| return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0); |
| } |
| } |
| |
| return kDefaultArgumentSize; |
| } |
| |
| // This function returns the default string buffer size used by MakeString. |
| template <typename... Args> |
| constexpr size_t DefaultStringBufferSize() { |
| return std::max((size_t(1) + ... + ArgLength<Args>()), |
| kDefaultMinimumStringBufferSize); |
| } |
| |
| // Internal version of MakeString with const reference arguments instead of |
| // deduced types, which include the lengths of string literals. Having this |
| // function can reduce code size. |
| template <size_t kBufferSize, typename... Args> |
| auto InitializeStringBuffer(const Args&... args) { |
| return (StringBuffer<kBufferSize>() << ... << args); |
| } |
| |
| } // namespace string_internal |
| |
| // Makes a StringBuffer with a string version of a series of values. This is |
| // useful for creating and initializing a StringBuffer or for conveniently |
| // getting a null-terminated string. For example: |
| // |
| // LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str()); |
| // |
| // By default, the buffer size is 24 bytes, large enough to fit any 64-bit |
| // integer. If string literal arguments are provided, the default size will be |
| // large enough to fit them and a null terminator, plus 4 additional bytes for |
| // each argument. To use a fixed buffer size, set the kBufferSize template |
| // argument. For example: |
| // |
| // // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes). |
| // auto sb = MakeString("1234567890", "1234567890", number, "!"); |
| // |
| // // Creates a 32-byte StringBuffer. |
| // auto sb = MakeString<32>("1234567890", "1234567890", number, "!"); |
| // |
| // Keep in mind that each argument to MakeString expands to a function call. |
| // MakeString may increase code size more than an equivalent pw::string::Format |
| // (or std::snprintf) call. |
| template <size_t kBufferSize = 0u, typename... Args> |
| auto MakeString(Args&&... args) { |
| constexpr size_t kSize = |
| kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>() |
| : kBufferSize; |
| return string_internal::InitializeStringBuffer<kSize>(args...); |
| } |
| |
| } // namespace pw |