1 // Copyright 2019 The Pigweed Authors
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
7 // https://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
21 #include <string_view>
22 #include <type_traits>
25 #include "pw_preprocessor/compiler.h"
26 #include "pw_status/status.h"
27 #include "pw_status/status_with_size.h"
28 #include "pw_string/to_string.h"
32 // StringBuilder facilitates building formatted strings in a fixed-size buffer.
33 // StringBuilders are always null terminated (unless they are constructed with
34 // an empty buffer) and never overflow. Status is tracked for each operation and
35 // an overall status is maintained, which reflects the most recent error.
37 // A StringBuilder does not own the buffer it writes to. It can be used to write
38 // strings to any buffer. The StringBuffer template class, defined below,
39 // allocates a buffer alongside a StringBuilder.
41 // StringBuilder supports C++-style << output, similar to std::ostringstream. It
42 // also supports std::string-like append functions and printf-style output.
44 // StringBuilder uses the ToString function to support arbitrary types. Defining
45 // a ToString template specialization overload in the pw namespace allows
46 // writing that type to a StringBuilder with <<.
48 // For example, the following ToString overload allows writing MyStatus objects
54 // StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
55 // return CopyString(MyStatusString(value), buffer);
60 // For complex types, it may be easier to override StringBuilder's << operator,
61 // similar to the standard library's std::ostream. For example:
65 // StringBuilder& operator<<(StringBuilder& sb, const MyType& value) {
66 // return sb << "MyType(" << value.foo << ", " << value.bar << ')';
71 // Alternately, complex types may use a StringBuilder in their ToString, but it
72 // is likely to be simpler to override StringBuilder's operator<<.
74 // StringBuilder is safe, flexible, and results in much smaller code size than
75 // using std::ostringstream. However, applications sensitive to code size should
76 // use StringBuilder with care.
78 // The fixed code size cost of StringBuilder is significant, though smaller than
79 // std::snprintf. Using StringBuilder's << and append methods exclusively in
80 // place of snprintf reduces code size, but snprintf may be difficult to avoid.
82 // The incremental code size cost of StringBuilder is comparable to snprintf if
83 // errors are handled. Each argument to StringBuilder's << expands to a function
84 // call, but one or two StringBuilder appends may have a smaller code size
85 // impact than a single snprintf call. See the size report for further analysis.
88 // Creates an empty StringBuilder.
89 constexpr StringBuilder(std::span<char> buffer) : buffer_(buffer), size_(0) {
92 StringBuilder(std::span<std::byte> buffer)
94 {reinterpret_cast<char*>(buffer.data()), buffer.size_bytes()}) {}
96 // Disallow copy/assign to avoid confusion about where the string is actually
97 // stored. StringBuffers may be copied into one another.
98 StringBuilder(const StringBuilder&) = delete;
100 StringBuilder& operator=(const StringBuilder&) = delete;
102 // Returns the contents of the string buffer. Always null-terminated.
103 const char* data() const { return buffer_.data(); }
104 const char* c_str() const { return data(); }
106 // Returns a std::string_view of the contents of this StringBuilder. The
107 // std::string_view is invalidated if the StringBuilder contents change.
108 std::string_view view() const { return std::string_view(data(), size()); }
110 // Allow implicit conversions to std::string_view so StringBuilders can be
111 // passed into functions that take a std::string_view.
112 operator std::string_view() const { return view(); }
114 // Returns a std::span<const std::byte> representation of this StringBuffer.
115 std::span<const std::byte> as_bytes() const {
116 return std::span(reinterpret_cast<const std::byte*>(buffer_.data()), size_);
119 // Returns the StringBuilder's status, which reflects the most recent error
120 // that occurred while updating the string. After an update fails, the status
121 // remains non-OK until it is cleared with clear() or clear_status(). Returns:
123 // OK if no errors have occurred
124 // RESOURCE_EXHAUSTED if output to the StringBuilder was truncated
125 // INVALID_ARGUMENT if printf-style formatting failed
126 // OUT_OF_RANGE if an operation outside the buffer was attempted
128 Status status() const { return status_; }
130 // Returns status() and size() as a StatusWithSize.
131 StatusWithSize status_with_size() const {
132 return StatusWithSize(status_, size_);
135 // The status from the last operation. May be OK while status() is not OK.
136 Status last_status() const { return last_status_; }
138 // True if status() is OkStatus().
139 bool ok() const { return status_.ok(); }
141 // True if the string is empty.
142 bool empty() const { return size() == 0u; }
144 // Returns the current length of the string, excluding the null terminator.
145 size_t size() const { return size_; }
147 // Returns the maximum length of the string, excluding the null terminator.
148 size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; }
150 // Clears the string and resets its error state.
153 // Sets the statuses to OkStatus();
154 void clear_status() {
155 status_ = OkStatus();
156 last_status_ = OkStatus();
159 // Appends a single character. Stets the status to RESOURCE_EXHAUSTED if the
160 // character cannot be added because the buffer is full.
161 void push_back(char ch) { append(1, ch); }
163 // Removes the last character. Sets the status to OUT_OF_RANGE if the buffer
164 // is empty (in which case the unsigned overflow is intentional).
165 void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
169 // Appends the provided character count times.
170 StringBuilder& append(size_t count, char ch);
172 // Appends count characters from str to the end of the StringBuilder. If count
173 // exceeds the remaining space in the StringBuffer, max_size() - size()
174 // characters are appended and the status is set to RESOURCE_EXHAUSTED.
176 // str is not considered null-terminated and may contain null characters.
177 StringBuilder& append(const char* str, size_t count);
179 // Appends characters from the null-terminated string to the end of the
180 // StringBuilder. If the string's length exceeds the remaining space in the
181 // buffer, max_size() - size() characters are copied and the status is set to
182 // RESOURCE_EXHAUSTED.
184 // This function uses string::Length instead of std::strlen to avoid unbounded
185 // reads if the string is not null terminated.
186 StringBuilder& append(const char* str);
188 // Appends a std::string_view to the end of the StringBuilder.
189 StringBuilder& append(const std::string_view& str);
191 // Appends a substring from the std::string_view to the StringBuilder. Copies
192 // up to count characters starting from pos to the end of the StringBuilder.
193 // If pos > str.size(), sets the status to OUT_OF_RANGE.
194 StringBuilder& append(const std::string_view& str,
196 size_t count = std::string_view::npos);
198 // Appends to the end of the StringBuilder using the << operator. This enables
199 // C++ stream-style formatted to StringBuilders.
200 template <typename T>
201 StringBuilder& operator<<(const T& value) {
202 // For std::string_view-compatible types, use the append function, which
203 // gives smaller code size.
204 if constexpr (std::is_convertible_v<T, std::string_view>) {
207 HandleStatusWithSize(ToString(value, buffer_.subspan(size_)));
212 // Provide a few additional operator<< overloads that reduce code size.
213 StringBuilder& operator<<(bool value) {
214 return append(value ? "true" : "false");
217 StringBuilder& operator<<(char value) {
222 StringBuilder& operator<<(std::nullptr_t) {
223 return append(string::kNullPointerString);
226 StringBuilder& operator<<(Status status) { return *this << status.str(); }
228 // Appends a printf-style string to the end of the StringBuilder. If the
229 // formatted string does not fit, the results are truncated and the status is
230 // set to RESOURCE_EXHAUSTED.
232 // Internally, calls string::Format, which calls std::vsnprintf.
233 PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...);
235 // Appends a vsnprintf-style string with va_list arguments to the end of the
236 // StringBuilder. If the formatted string does not fit, the results are
237 // truncated and the status is set to RESOURCE_EXHAUSTED.
239 // Internally, calls string::Format, which calls std::vsnprintf.
240 StringBuilder& FormatVaList(const char* format, va_list args);
242 // Sets the StringBuilder's size. This function only truncates; if
243 // new_size > size(), it sets status to OUT_OF_RANGE and does nothing.
244 void resize(size_t new_size);
247 // Functions to support StringBuffer copies.
248 constexpr StringBuilder(std::span<char> buffer, const StringBuilder& other)
251 status_(other.status_),
252 last_status_(other.last_status_) {}
254 void CopySizeAndStatus(const StringBuilder& other);
257 size_t ResizeAndTerminate(size_t chars_to_append);
259 void HandleStatusWithSize(StatusWithSize written);
261 constexpr void NullTerminate() {
262 if (!buffer_.empty()) {
263 buffer_[size_] = '\0';
267 void SetErrorStatus(Status status);
269 const std::span<char> buffer_;
276 // StringBuffers declare a buffer along with a StringBuilder. StringBuffer can
277 // be used as a statically allocated replacement for std::ostringstream or
278 // std::string. For example:
280 // StringBuffer<32> str;
281 // str << "The answer is " << number << "!"; // with number = 42
282 // str.c_str(); // null terminated C string "The answer is 42."
283 // str.view(); // std::string_view of "The answer is 42."
285 template <size_t kSizeBytes>
286 class StringBuffer : public StringBuilder {
288 StringBuffer() : StringBuilder(buffer_) {}
290 // StringBuffers of the same size may be copied and assigned into one another.
291 StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) {
295 // A smaller StringBuffer may be copied or assigned into a larger one.
296 template <size_t kOtherSizeBytes>
297 StringBuffer(const StringBuffer<kOtherSizeBytes>& other)
298 : StringBuilder(buffer_, other) {
299 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
300 "A StringBuffer cannot be copied into a smaller buffer");
304 template <size_t kOtherSizeBytes>
305 StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) {
306 assign<kOtherSizeBytes>(other);
310 StringBuffer& operator=(const StringBuffer& other) {
311 assign<kSizeBytes>(other);
315 template <size_t kOtherSizeBytes>
316 StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) {
317 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
318 "A StringBuffer cannot be copied into a smaller buffer");
319 CopySizeAndStatus(other);
324 // Returns the maximum length of the string, excluding the null terminator.
325 static constexpr size_t max_size() { return kSizeBytes - 1; }
327 // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
328 // append calls and stream-style operations.
329 template <typename... Args>
330 StringBuffer& append(Args&&... args) {
331 StringBuilder::append(std::forward<Args>(args)...);
335 template <typename T>
336 StringBuffer& operator<<(T&& value) {
337 static_cast<StringBuilder&>(*this) << std::forward<T>(value);
342 template <size_t kOtherSize>
343 void CopyContents(const StringBuffer<kOtherSize>& other) {
344 std::memcpy(buffer_, other.data(), other.size() + 1); // include the \0
347 static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
348 char buffer_[kSizeBytes];
351 namespace string_internal {
353 // Internal code for determining the default size of StringBuffers created with
356 // StringBuffers created with MakeString default to at least 24 bytes. This is
357 // large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded
358 // up to the nearest multiple of 4.
359 inline constexpr size_t kDefaultMinimumStringBufferSize = 24;
361 // By default, MakeString uses a buffer size large enough to fit all string
362 // literal arguments. ArgLength uses this value as an estimate of the number of
363 // characters needed to represent a non-string argument.
364 inline constexpr size_t kDefaultArgumentSize = 4;
366 // Returns a string literal's length or kDefaultArgumentSize for non-strings.
367 template <typename T>
368 constexpr size_t ArgLength() {
369 using Arg = std::remove_reference_t<T>;
371 // If the argument is an array of const char, assume it is a string literal.
372 if constexpr (std::is_array_v<Arg>) {
373 using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>;
375 if constexpr (std::is_same_v<Element, const char>) {
376 return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0);
380 return kDefaultArgumentSize;
383 // This function returns the default string buffer size used by MakeString.
384 template <typename... Args>
385 constexpr size_t DefaultStringBufferSize() {
386 return std::max((size_t(1) + ... + ArgLength<Args>()),
387 kDefaultMinimumStringBufferSize);
390 // Internal version of MakeString with const reference arguments instead of
391 // deduced types, which include the lengths of string literals. Having this
392 // function can reduce code size.
393 template <size_t kBufferSize, typename... Args>
394 auto InitializeStringBuffer(const Args&... args) {
395 return (StringBuffer<kBufferSize>() << ... << args);
398 } // namespace string_internal
400 // Makes a StringBuffer with a string version of a series of values. This is
401 // useful for creating and initializing a StringBuffer or for conveniently
402 // getting a null-terminated string. For example:
404 // LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str());
406 // By default, the buffer size is 24 bytes, large enough to fit any 64-bit
407 // integer. If string literal arguments are provided, the default size will be
408 // large enough to fit them and a null terminator, plus 4 additional bytes for
409 // each argument. To use a fixed buffer size, set the kBufferSize template
410 // argument. For example:
412 // // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes).
413 // auto sb = MakeString("1234567890", "1234567890", number, "!");
415 // // Creates a 32-byte StringBuffer.
416 // auto sb = MakeString<32>("1234567890", "1234567890", number, "!");
418 // Keep in mind that each argument to MakeString expands to a function call.
419 // MakeString may increase code size more than an equivalent pw::string::Format
420 // (or std::snprintf) call.
421 template <size_t kBufferSize = 0u, typename... Args>
422 auto MakeString(Args&&... args) {
423 constexpr size_t kSize =
424 kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>()
426 return string_internal::InitializeStringBuffer<kSize>(args...);