xref: /aosp_15_r20/external/pigweed/pw_string/public/pw_string/string_builder.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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