1 // Copyright 2019 The Pigweed Authors
2 //
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
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
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
13 // the License.
14 #pragma once
15 /// @file pw_string/string_builder.h
16 ///
17 /// @brief `pw::StringBuilder` facilitates creating formatted strings in a
18 /// fixed-sized buffer or in a `pw::InlineString`. It is designed to give the
19 /// flexibility of std::ostringstream, but with a small footprint.
20
21 #include <algorithm>
22 #include <cstdarg>
23 #include <cstddef>
24 #include <cstring>
25 #include <string_view>
26 #include <type_traits>
27 #include <utility>
28
29 #include "pw_preprocessor/compiler.h"
30 #include "pw_span/span.h"
31 #include "pw_status/status.h"
32 #include "pw_status/status_with_size.h"
33 #include "pw_string/string.h"
34 #include "pw_string/to_string.h"
35
36 namespace pw {
37
38 /// @class StringBuilder
39 ///
40 /// `pw::StringBuilder` instances are always null-terminated (unless they are
41 /// constructed with an empty buffer) and never overflow. Status is tracked for
42 /// each operation and an overall status is maintained, which reflects the most
43 /// recent error.
44 ///
45 /// `pw::StringBuilder` does not own the buffer it writes to. It can be used
46 /// to write strings to any buffer. The `pw::StringBuffer` template class,
47 /// defined below, allocates a buffer alongside a `pw::StringBuilder`.
48 ///
49 /// `pw::StringBuilder` supports C++-style `<<` output, similar to
50 /// `std::ostringstream`. It also supports append functions like `std::string`
51 /// and `printf`-style output.
52 ///
53 /// Support for custom types is added by overloading `operator<<` in the same
54 /// namespace as the custom type. For example:
55 ///
56 /// @code
57 /// namespace my_project {
58 ///
59 /// struct MyType {
60 /// int foo;
61 /// const char* bar;
62 /// };
63 ///
64 /// pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value)
65 /// {
66 /// return sb << "MyType(" << value.foo << ", " << value.bar << ')';
67 /// }
68 ///
69 /// } // namespace my_project
70 /// @endcode
71 ///
72 /// The `ToString` template function can be specialized to support custom types
73 /// with `pw::StringBuilder`, though overloading `operator<<` is generally
74 /// preferred. For example:
75 ///
76 /// @code
77 /// namespace pw {
78 ///
79 /// template <>
80 /// StatusWithSize ToString<MyStatus>(MyStatus value, span<char> buffer) {
81 /// return Copy(MyStatusString(value), buffer);
82 /// }
83 ///
84 /// } // namespace pw
85 /// @endcode
86 ///
87 class StringBuilder {
88 public:
89 /// Creates an empty `pw::StringBuilder`.
StringBuilder(span<char> buffer)90 explicit constexpr StringBuilder(span<char> buffer)
91 : buffer_(buffer), size_(&inline_size_), inline_size_(0) {
92 NullTerminate();
93 }
94
StringBuilder(span<std::byte> buffer)95 explicit StringBuilder(span<std::byte> buffer)
96 : StringBuilder(
97 {reinterpret_cast<char*>(buffer.data()), buffer.size_bytes()}) {}
98
StringBuilder(InlineString<> & string)99 explicit constexpr StringBuilder(InlineString<>& string)
100 : buffer_(string.data(), string.max_size() + 1),
101 size_(&string.length_),
102 inline_size_(0) {}
103
104 /// Disallow copy/assign to avoid confusion about where the string is actually
105 /// stored. `pw::StringBuffer` instances may be copied into one another.
106 StringBuilder(const StringBuilder&) = delete;
107
108 StringBuilder& operator=(const StringBuilder&) = delete;
109
110 /// @fn data
111 /// @fn c_str
112 ///
113 /// Returns the contents of the string buffer. Always null-terminated.
data()114 const char* data() const { return buffer_.data(); }
c_str()115 const char* c_str() const { return data(); }
116
117 /// Returns a `std::string_view` of the contents of this `pw::StringBuilder`.
118 /// The `std::string_view` is invalidated if the `pw::StringBuilder` contents
119 /// change.
view()120 std::string_view view() const { return std::string_view(data(), size()); }
121
122 /// Allow implicit conversions to `std::string_view` so `pw::StringBuilder`
123 /// instances can be passed into functions that take a `std::string_view`.
string_view()124 operator std::string_view() const { return view(); }
125
126 /// Returns a `span<const std::byte>` representation of this
127 /// `pw::StringBuffer`.
as_bytes()128 span<const std::byte> as_bytes() const {
129 return span(reinterpret_cast<const std::byte*>(buffer_.data()), size());
130 }
131
132 /// Returns the status of `pw::StringBuilder`, which reflects the most recent
133 /// error that occurred while updating the string. After an update fails, the
134 /// status remains non-OK until it is cleared with
135 /// `pw::StringBuilder::clear()` or `pw::StringBuilder::clear_status()`.
136 ///
137 /// @returns @rst
138 ///
139 /// .. pw-status-codes::
140 ///
141 /// OK: No errors have occurred.
142 ///
143 /// RESOURCE_EXHAUSTED: Output to the ``StringBuilder`` was truncated.
144 ///
145 /// INVALID_ARGUMENT: ``printf``-style formatting failed.
146 ///
147 /// OUT_OF_RANGE: An operation outside the buffer was attempted.
148 ///
149 /// @endrst
status()150 Status status() const { return static_cast<Status::Code>(status_); }
151
152 /// Returns `status()` and `size()` as a `StatusWithSize`.
status_with_size()153 StatusWithSize status_with_size() const {
154 return StatusWithSize(status(), size());
155 }
156
157 /// The status from the last operation. May be OK while `status()` is not OK.
last_status()158 Status last_status() const { return static_cast<Status::Code>(last_status_); }
159
160 /// True if `status()` is `OkStatus()`.
ok()161 bool ok() const { return status().ok(); }
162
163 /// True if the string is empty.
empty()164 bool empty() const { return size() == 0u; }
165
166 /// Returns the current length of the string, excluding the null terminator.
size()167 size_t size() const { return *size_; }
168
169 /// Returns the maximum length of the string, excluding the null terminator.
max_size()170 size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; }
171
172 /// Clears the string and resets its error state.
173 void clear();
174
175 /// Sets the statuses to `OkStatus()`;
clear_status()176 void clear_status() {
177 status_ = static_cast<unsigned char>(OkStatus().code());
178 last_status_ = static_cast<unsigned char>(OkStatus().code());
179 }
180
181 /// Appends a single character. Sets the status to `RESOURCE_EXHAUSTED` if the
182 /// character cannot be added because the buffer is full.
push_back(char ch)183 void push_back(char ch) { append(1, ch); }
184
185 /// Removes the last character. Sets the status to `OUT_OF_RANGE` if the
186 /// buffer is empty (in which case the unsigned overflow is intentional).
pop_back()187 void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
188 resize(size() - 1);
189 }
190
191 /// Appends the provided character `count` times.
192 StringBuilder& append(size_t count, char ch);
193
194 /// Appends `count` characters from `str` to the end of the `StringBuilder`.
195 /// If count exceeds the remaining space in the `StringBuffer`,
196 /// `max_size() - size()` characters are appended and the status is set to
197 /// `RESOURCE_EXHAUSTED`.
198 ///
199 /// `str` is not considered null-terminated and may contain null characters.
200 StringBuilder& append(const char* str, size_t count);
201
202 /// Appends characters from the null-terminated string to the end of the
203 /// `StringBuilder`. If the string's length exceeds the remaining space in the
204 /// buffer, `max_size() - size()` characters are copied and the status is
205 /// set to `RESOURCE_EXHAUSTED`.
206 ///
207 /// This function uses `string::Length` instead of `std::strlen` to avoid
208 /// unbounded reads if the string is not null-terminated.
209 StringBuilder& append(const char* str);
210
211 /// Appends a `std::string_view` to the end of the `StringBuilder`.
212 StringBuilder& append(std::string_view str);
213
214 /// Appends a substring from the `std::string_view` to the `StringBuilder`.
215 /// Copies up to count characters starting from `pos` to the end of the
216 /// `StringBuilder`. If `pos > str.size()`, sets the status to `OUT_OF_RANGE`.
217 StringBuilder& append(std::string_view str,
218 size_t pos,
219 size_t count = std::string_view::npos);
220
221 /// Appends to the end of the `StringBuilder` using the `<<` operator. This
222 /// enables C++ stream-style formatted to `StringBuilder` instances.
223 template <typename T>
224 StringBuilder& operator<<(const T& value) {
225 /// For types compatible with `std::string_view`, use the `append` function,
226 /// which gives smaller code size.
227 if constexpr (std::is_convertible_v<T, std::string_view>) {
228 append(value);
229 } else if constexpr (std::is_convertible_v<T, span<const std::byte>>) {
230 WriteBytes(value);
231 } else {
232 HandleStatusWithSize(ToString(value, buffer_.subspan(size())));
233 }
234 return *this;
235 }
236
237 /// Provide a few additional `operator<<` overloads that reduce code size.
238 StringBuilder& operator<<(bool value) {
239 return append(value ? "true" : "false");
240 }
241
242 StringBuilder& operator<<(char value) {
243 push_back(value);
244 return *this;
245 }
246
247 StringBuilder& operator<<(std::nullptr_t) {
248 return append(string::kNullPointerString);
249 }
250
251 StringBuilder& operator<<(Status status) { return *this << status.str(); }
252
253 /// @fn pw::StringBuilder::Format
254 /// Appends a `printf`-style string to the end of the `StringBuilder`. If the
255 /// formatted string does not fit, the results are truncated and the status is
256 /// set to `RESOURCE_EXHAUSTED`.
257 ///
258 /// @param format The format string
259 /// @param ... Arguments for format specification
260 ///
261 /// @returns `StringBuilder&`
262 ///
263 /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`.
264 PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...);
265
266 /// Appends a `vsnprintf`-style string with `va_list` arguments to the end of
267 /// the `StringBuilder`. If the formatted string does not fit, the results are
268 /// truncated and the status is set to `RESOURCE_EXHAUSTED`.
269 ///
270 /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`.
271 PW_PRINTF_FORMAT(2, 0)
272 StringBuilder& FormatVaList(const char* format, va_list args);
273
274 /// Sets the size of the `StringBuilder`. This function only truncates; if
275 /// `new_size > size()`, it sets status to `OUT_OF_RANGE` and does nothing.
276 void resize(size_t new_size);
277
278 protected:
279 /// Functions to support `StringBuffer` copies.
StringBuilder(span<char> buffer,const StringBuilder & other)280 constexpr StringBuilder(span<char> buffer, const StringBuilder& other)
281 : buffer_(buffer),
282 size_(&inline_size_),
283 inline_size_(*other.size_),
284 status_(other.status_),
285 last_status_(other.last_status_) {}
286
287 void CopySizeAndStatus(const StringBuilder& other);
288
289 private:
290 /// Statuses are stored as an `unsigned char` so they pack into a single word.
StatusCode(Status status)291 static constexpr unsigned char StatusCode(Status status) {
292 return static_cast<unsigned char>(status.code());
293 }
294
295 void WriteBytes(span<const std::byte> data);
296
297 size_t ResizeAndTerminate(size_t chars_to_append);
298
299 void HandleStatusWithSize(StatusWithSize written);
300
NullTerminate()301 constexpr void NullTerminate() {
302 if (!buffer_.empty()) {
303 buffer_[size()] = '\0';
304 }
305 }
306
307 void SetErrorStatus(Status status);
308
309 const span<char> buffer_;
310
311 InlineString<>::size_type* size_;
312
313 // Place the `inline_size_`, `status_`, and `last_status_` members together
314 // and use `unsigned char` for the status codes so these members can be
315 // packed into a single word.
316 InlineString<>::size_type inline_size_;
317 unsigned char status_ = StatusCode(OkStatus());
318 unsigned char last_status_ = StatusCode(OkStatus());
319 };
320
321 // StringBuffer declares a buffer along with a StringBuilder. StringBuffer
322 // can be used as a statically allocated replacement for std::ostringstream or
323 // std::string. For example:
324 //
325 // StringBuffer<32> str;
326 // str << "The answer is " << number << "!"; // with number = 42
327 // str.c_str(); // null terminated C string "The answer is 42."
328 // str.view(); // std::string_view of "The answer is 42."
329 //
330 template <size_t kSizeBytes>
331 class StringBuffer : public StringBuilder {
332 public:
StringBuffer()333 StringBuffer() : StringBuilder(buffer_) {}
334
335 // StringBuffers of the same size may be copied and assigned into one another.
StringBuffer(const StringBuffer & other)336 StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) {
337 CopyContents(other);
338 }
339
340 // A smaller StringBuffer may be copied or assigned into a larger one.
341 template <size_t kOtherSizeBytes>
StringBuffer(const StringBuffer<kOtherSizeBytes> & other)342 StringBuffer(const StringBuffer<kOtherSizeBytes>& other)
343 : StringBuilder(buffer_, other) {
344 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
345 "A StringBuffer cannot be copied into a smaller buffer");
346 CopyContents(other);
347 }
348
349 template <size_t kOtherSizeBytes>
350 StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) {
351 assign<kOtherSizeBytes>(other);
352 return *this;
353 }
354
355 StringBuffer& operator=(const StringBuffer& other) {
356 assign<kSizeBytes>(other);
357 return *this;
358 }
359
360 template <size_t kOtherSizeBytes>
assign(const StringBuffer<kOtherSizeBytes> & other)361 StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) {
362 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
363 "A StringBuffer cannot be copied into a smaller buffer");
364 CopySizeAndStatus(other);
365 CopyContents(other);
366 return *this;
367 }
368
369 /// StringBuffers are not movable: the underlying data must be copied.
370 StringBuffer(StringBuffer&& other) = delete;
371
372 /// StringBuffers are not movable: the underlying data must be copied.
373 StringBuffer& operator=(StringBuffer&& other) = delete;
374
375 // Returns the maximum length of the string, excluding the null terminator.
max_size()376 static constexpr size_t max_size() { return kSizeBytes - 1; }
377
378 // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
379 // append calls and stream-style operations.
380 template <typename... Args>
append(Args &&...args)381 StringBuffer& append(Args&&... args) {
382 StringBuilder::append(std::forward<Args>(args)...);
383 return *this;
384 }
385
386 template <typename T>
387 StringBuffer& operator<<(T&& value) {
388 static_cast<StringBuilder&>(*this) << std::forward<T>(value);
389 return *this;
390 }
391
392 private:
393 template <size_t kOtherSize>
CopyContents(const StringBuffer<kOtherSize> & other)394 void CopyContents(const StringBuffer<kOtherSize>& other) {
395 std::memcpy(buffer_, other.data(), other.size() + 1); // include the \0
396 }
397
398 static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
399 char buffer_[kSizeBytes];
400 };
401
402 namespace string_internal {
403
404 // Internal code for determining the default size of StringBuffers created with
405 // MakeString.
406 //
407 // StringBuffers created with MakeString default to at least 24 bytes. This is
408 // large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded
409 // up to the nearest multiple of 4.
410 inline constexpr size_t kDefaultMinimumStringBufferSize = 24;
411
412 // By default, MakeString uses a buffer size large enough to fit all string
413 // literal arguments. ArgLength uses this value as an estimate of the number of
414 // characters needed to represent a non-string argument.
415 inline constexpr size_t kDefaultArgumentSize = 4;
416
417 // Returns a string literal's length or kDefaultArgumentSize for non-strings.
418 template <typename T>
ArgLength()419 constexpr size_t ArgLength() {
420 using Arg = std::remove_reference_t<T>;
421
422 // If the argument is an array of const char, assume it is a string literal.
423 if constexpr (std::is_array_v<Arg>) {
424 using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>;
425
426 if constexpr (std::is_same_v<Element, const char>) {
427 return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0);
428 }
429 }
430
431 return kDefaultArgumentSize;
432 }
433
434 // This function returns the default string buffer size used by MakeString.
435 template <typename... Args>
DefaultStringBufferSize()436 constexpr size_t DefaultStringBufferSize() {
437 return std::max((size_t(1) + ... + ArgLength<Args>()),
438 kDefaultMinimumStringBufferSize);
439 }
440
441 // Internal version of MakeString with const reference arguments instead of
442 // deduced types, which include the lengths of string literals. Having this
443 // function can reduce code size.
444 template <size_t kBufferSize, typename... Args>
InitializeStringBuffer(const Args &...args)445 auto InitializeStringBuffer(const Args&... args) {
446 return (StringBuffer<kBufferSize>() << ... << args);
447 }
448
449 } // namespace string_internal
450
451 // Makes a StringBuffer with a string version of a series of values. This is
452 // useful for creating and initializing a StringBuffer or for conveniently
453 // getting a null-terminated string. For example:
454 //
455 // LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str());
456 //
457 // By default, the buffer size is 24 bytes, large enough to fit any 64-bit
458 // integer. If string literal arguments are provided, the default size will be
459 // large enough to fit them and a null terminator, plus 4 additional bytes for
460 // each argument. To use a fixed buffer size, set the kBufferSize template
461 // argument. For example:
462 //
463 // // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes).
464 // auto sb = MakeString("1234567890", "1234567890", number, "!");
465 //
466 // // Creates a 32-byte StringBuffer.
467 // auto sb = MakeString<32>("1234567890", "1234567890", number, "!");
468 //
469 // Keep in mind that each argument to MakeString expands to a function call.
470 // MakeString may increase code size more than an equivalent pw::string::Format
471 // (or std::snprintf) call.
472 template <size_t kBufferSize = 0u, typename... Args>
MakeString(Args &&...args)473 auto MakeString(Args&&... args) {
474 constexpr size_t kSize =
475 kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>()
476 : kBufferSize;
477 return string_internal::InitializeStringBuffer<kSize>(args...);
478 }
479
480 } // namespace pw
481