• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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