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 `OK` if no errors have occurred; `RESOURCE_EXHAUSTED` if output
138 /// to the `StringBuilder` was truncated; `INVALID_ARGUMENT` if `printf`-style
139 /// formatting failed; `OUT_OF_RANGE` if an operation outside the buffer was
140 /// attempted.
status()141 Status status() const { return static_cast<Status::Code>(status_); }
142
143 /// Returns `status()` and `size()` as a `StatusWithSize`.
status_with_size()144 StatusWithSize status_with_size() const {
145 return StatusWithSize(status(), size());
146 }
147
148 /// The status from the last operation. May be OK while `status()` is not OK.
last_status()149 Status last_status() const { return static_cast<Status::Code>(last_status_); }
150
151 /// True if `status()` is `OkStatus()`.
ok()152 bool ok() const { return status().ok(); }
153
154 /// True if the string is empty.
empty()155 bool empty() const { return size() == 0u; }
156
157 /// Returns the current length of the string, excluding the null terminator.
size()158 size_t size() const { return *size_; }
159
160 /// Returns the maximum length of the string, excluding the null terminator.
max_size()161 size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; }
162
163 /// Clears the string and resets its error state.
164 void clear();
165
166 /// Sets the statuses to `OkStatus()`;
clear_status()167 void clear_status() {
168 status_ = static_cast<unsigned char>(OkStatus().code());
169 last_status_ = static_cast<unsigned char>(OkStatus().code());
170 }
171
172 /// Appends a single character. Sets the status to `RESOURCE_EXHAUSTED` if the
173 /// character cannot be added because the buffer is full.
push_back(char ch)174 void push_back(char ch) { append(1, ch); }
175
176 /// Removes the last character. Sets the status to `OUT_OF_RANGE` if the
177 /// buffer is empty (in which case the unsigned overflow is intentional).
pop_back()178 void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
179 resize(size() - 1);
180 }
181
182 /// Appends the provided character `count` times.
183 StringBuilder& append(size_t count, char ch);
184
185 /// Appends `count` characters from `str` to the end of the `StringBuilder`.
186 /// If count exceeds the remaining space in the `StringBuffer`,
187 /// `max_size() - size()` characters are appended and the status is set to
188 /// `RESOURCE_EXHAUSTED`.
189 ///
190 /// `str` is not considered null-terminated and may contain null characters.
191 StringBuilder& append(const char* str, size_t count);
192
193 /// Appends characters from the null-terminated string to the end of the
194 /// `StringBuilder`. If the string's length exceeds the remaining space in the
195 /// buffer, `max_size() - size()` characters are copied and the status is
196 /// set to `RESOURCE_EXHAUSTED`.
197 ///
198 /// This function uses `string::Length` instead of `std::strlen` to avoid
199 /// unbounded reads if the string is not null-terminated.
200 StringBuilder& append(const char* str);
201
202 /// Appends a `std::string_view` to the end of the `StringBuilder`.
203 StringBuilder& append(const std::string_view& str);
204
205 /// Appends a substring from the `std::string_view` to the `StringBuilder`.
206 /// Copies up to count characters starting from `pos` to the end of the
207 /// `StringBuilder`. If `pos > str.size()`, sets the status to `OUT_OF_RANGE`.
208 StringBuilder& append(const std::string_view& str,
209 size_t pos,
210 size_t count = std::string_view::npos);
211
212 /// Appends to the end of the `StringBuilder` using the `<<` operator. This
213 /// enables C++ stream-style formatted to `StringBuilder` instances.
214 template <typename T>
215 StringBuilder& operator<<(const T& value) {
216 /// For types compatible with `std::string_view`, use the `append` function,
217 /// which gives smaller code size.
218 if constexpr (std::is_convertible_v<T, std::string_view>) {
219 append(value);
220 } else if constexpr (std::is_convertible_v<T, span<const std::byte>>) {
221 WriteBytes(value);
222 } else {
223 HandleStatusWithSize(ToString(value, buffer_.subspan(size())));
224 }
225 return *this;
226 }
227
228 /// Provide a few additional `operator<<` overloads that reduce code size.
229 StringBuilder& operator<<(bool value) {
230 return append(value ? "true" : "false");
231 }
232
233 StringBuilder& operator<<(char value) {
234 push_back(value);
235 return *this;
236 }
237
238 StringBuilder& operator<<(std::nullptr_t) {
239 return append(string::kNullPointerString);
240 }
241
242 StringBuilder& operator<<(Status status) { return *this << status.str(); }
243
244 /// @fn pw::StringBuilder::Format
245 /// Appends a `printf`-style string to the end of the `StringBuilder`. If the
246 /// formatted string does not fit, the results are truncated and the status is
247 /// set to `RESOURCE_EXHAUSTED`.
248 ///
249 /// @param format The format string
250 /// @param ... Arguments for format specification
251 ///
252 /// @returns `StringBuilder&`
253 ///
254 /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`.
255 PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...);
256
257 /// Appends a `vsnprintf`-style string with `va_list` arguments to the end of
258 /// the `StringBuilder`. If the formatted string does not fit, the results are
259 /// truncated and the status is set to `RESOURCE_EXHAUSTED`.
260 ///
261 /// @note Internally, calls `string::Format`, which calls `std::vsnprintf`.
262 PW_PRINTF_FORMAT(2, 0)
263 StringBuilder& FormatVaList(const char* format, va_list args);
264
265 /// Sets the size of the `StringBuilder`. This function only truncates; if
266 /// `new_size > size()`, it sets status to `OUT_OF_RANGE` and does nothing.
267 void resize(size_t new_size);
268
269 protected:
270 /// Functions to support `StringBuffer` copies.
StringBuilder(span<char> buffer,const StringBuilder & other)271 constexpr StringBuilder(span<char> buffer, const StringBuilder& other)
272 : buffer_(buffer),
273 size_(&inline_size_),
274 inline_size_(*other.size_),
275 status_(other.status_),
276 last_status_(other.last_status_) {}
277
278 void CopySizeAndStatus(const StringBuilder& other);
279
280 private:
281 /// Statuses are stored as an `unsigned char` so they pack into a single word.
StatusCode(Status status)282 static constexpr unsigned char StatusCode(Status status) {
283 return static_cast<unsigned char>(status.code());
284 }
285
286 void WriteBytes(span<const std::byte> data);
287
288 size_t ResizeAndTerminate(size_t chars_to_append);
289
290 void HandleStatusWithSize(StatusWithSize written);
291
NullTerminate()292 constexpr void NullTerminate() {
293 if (!buffer_.empty()) {
294 buffer_[size()] = '\0';
295 }
296 }
297
298 void SetErrorStatus(Status status);
299
300 const span<char> buffer_;
301
302 InlineString<>::size_type* size_;
303
304 // Place the `inline_size_`, `status_`, and `last_status_` members together
305 // and use `unsigned char` for the status codes so these members can be
306 // packed into a single word.
307 InlineString<>::size_type inline_size_;
308 unsigned char status_ = StatusCode(OkStatus());
309 unsigned char last_status_ = StatusCode(OkStatus());
310 };
311
312 // StringBuffer declares a buffer along with a StringBuilder. StringBuffer
313 // can be used as a statically allocated replacement for std::ostringstream or
314 // std::string. For example:
315 //
316 // StringBuffer<32> str;
317 // str << "The answer is " << number << "!"; // with number = 42
318 // str.c_str(); // null terminated C string "The answer is 42."
319 // str.view(); // std::string_view of "The answer is 42."
320 //
321 template <size_t kSizeBytes>
322 class StringBuffer : public StringBuilder {
323 public:
StringBuffer()324 StringBuffer() : StringBuilder(buffer_) {}
325
326 // StringBuffers of the same size may be copied and assigned into one another.
StringBuffer(const StringBuffer & other)327 StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) {
328 CopyContents(other);
329 }
330
331 // A smaller StringBuffer may be copied or assigned into a larger one.
332 template <size_t kOtherSizeBytes>
StringBuffer(const StringBuffer<kOtherSizeBytes> & other)333 StringBuffer(const StringBuffer<kOtherSizeBytes>& other)
334 : StringBuilder(buffer_, other) {
335 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
336 "A StringBuffer cannot be copied into a smaller buffer");
337 CopyContents(other);
338 }
339
340 template <size_t kOtherSizeBytes>
341 StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) {
342 assign<kOtherSizeBytes>(other);
343 return *this;
344 }
345
346 StringBuffer& operator=(const StringBuffer& other) {
347 assign<kSizeBytes>(other);
348 return *this;
349 }
350
351 template <size_t kOtherSizeBytes>
assign(const StringBuffer<kOtherSizeBytes> & other)352 StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) {
353 static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
354 "A StringBuffer cannot be copied into a smaller buffer");
355 CopySizeAndStatus(other);
356 CopyContents(other);
357 return *this;
358 }
359
360 // Returns the maximum length of the string, excluding the null terminator.
max_size()361 static constexpr size_t max_size() { return kSizeBytes - 1; }
362
363 // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
364 // append calls and stream-style operations.
365 template <typename... Args>
append(Args &&...args)366 StringBuffer& append(Args&&... args) {
367 StringBuilder::append(std::forward<Args>(args)...);
368 return *this;
369 }
370
371 template <typename T>
372 StringBuffer& operator<<(T&& value) {
373 static_cast<StringBuilder&>(*this) << std::forward<T>(value);
374 return *this;
375 }
376
377 private:
378 template <size_t kOtherSize>
CopyContents(const StringBuffer<kOtherSize> & other)379 void CopyContents(const StringBuffer<kOtherSize>& other) {
380 std::memcpy(buffer_, other.data(), other.size() + 1); // include the \0
381 }
382
383 static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
384 char buffer_[kSizeBytes];
385 };
386
387 namespace string_internal {
388
389 // Internal code for determining the default size of StringBuffers created with
390 // MakeString.
391 //
392 // StringBuffers created with MakeString default to at least 24 bytes. This is
393 // large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded
394 // up to the nearest multiple of 4.
395 inline constexpr size_t kDefaultMinimumStringBufferSize = 24;
396
397 // By default, MakeString uses a buffer size large enough to fit all string
398 // literal arguments. ArgLength uses this value as an estimate of the number of
399 // characters needed to represent a non-string argument.
400 inline constexpr size_t kDefaultArgumentSize = 4;
401
402 // Returns a string literal's length or kDefaultArgumentSize for non-strings.
403 template <typename T>
ArgLength()404 constexpr size_t ArgLength() {
405 using Arg = std::remove_reference_t<T>;
406
407 // If the argument is an array of const char, assume it is a string literal.
408 if constexpr (std::is_array_v<Arg>) {
409 using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>;
410
411 if constexpr (std::is_same_v<Element, const char>) {
412 return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0);
413 }
414 }
415
416 return kDefaultArgumentSize;
417 }
418
419 // This function returns the default string buffer size used by MakeString.
420 template <typename... Args>
DefaultStringBufferSize()421 constexpr size_t DefaultStringBufferSize() {
422 return std::max((size_t(1) + ... + ArgLength<Args>()),
423 kDefaultMinimumStringBufferSize);
424 }
425
426 // Internal version of MakeString with const reference arguments instead of
427 // deduced types, which include the lengths of string literals. Having this
428 // function can reduce code size.
429 template <size_t kBufferSize, typename... Args>
InitializeStringBuffer(const Args &...args)430 auto InitializeStringBuffer(const Args&... args) {
431 return (StringBuffer<kBufferSize>() << ... << args);
432 }
433
434 } // namespace string_internal
435
436 // Makes a StringBuffer with a string version of a series of values. This is
437 // useful for creating and initializing a StringBuffer or for conveniently
438 // getting a null-terminated string. For example:
439 //
440 // LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str());
441 //
442 // By default, the buffer size is 24 bytes, large enough to fit any 64-bit
443 // integer. If string literal arguments are provided, the default size will be
444 // large enough to fit them and a null terminator, plus 4 additional bytes for
445 // each argument. To use a fixed buffer size, set the kBufferSize template
446 // argument. For example:
447 //
448 // // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes).
449 // auto sb = MakeString("1234567890", "1234567890", number, "!");
450 //
451 // // Creates a 32-byte StringBuffer.
452 // auto sb = MakeString<32>("1234567890", "1234567890", number, "!");
453 //
454 // Keep in mind that each argument to MakeString expands to a function call.
455 // MakeString may increase code size more than an equivalent pw::string::Format
456 // (or std::snprintf) call.
457 template <size_t kBufferSize = 0u, typename... Args>
MakeString(Args &&...args)458 auto MakeString(Args&&... args) {
459 constexpr size_t kSize =
460 kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>()
461 : kBufferSize;
462 return string_internal::InitializeStringBuffer<kSize>(args...);
463 }
464
465 } // namespace pw
466