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 16 // Provides the ToString function, which outputs string representations of 17 // arbitrary types to a buffer. 18 // 19 // ToString returns the number of characters written, excluding the null 20 // terminator, and a status. A null terminator is always written if the output 21 // buffer has room. 22 // 23 // ToString functions may be defined for any type. This is done by providing a 24 // ToString template specialization in the pw namespace. The specialization must 25 // follow ToString's semantics: 26 // 27 // 1. Always null terminate if the output buffer has room. 28 // 2. Return the number of characters written, excluding the null terminator, 29 // as a StatusWithSize. 30 // 3. If the buffer is too small to fit the output, return a StatusWithSize 31 // with the number of characters written and a status of 32 // RESOURCE_EXHAUSTED. Other status codes may be used for different errors. 33 // 34 // For example, providing the following specialization would allow ToString, and 35 // any classes that use it, to print instances of a custom type: 36 // 37 // namespace pw { 38 // 39 // template <> 40 // StatusWithSize ToString<SomeCustomType>(const SomeCustomType& value, 41 // span<char> buffer) { 42 // return /* ... implementation ... */; 43 // } 44 // 45 // } // namespace pw 46 // 47 // Note that none of the functions in this module use std::snprintf. ToString 48 // overloads may use snprintf if needed, but the ToString semantics must be 49 // maintained. 50 // 51 // ToString is a low-level function. To write complex objects to string, a 52 // StringBuilder may be easier to work with. StringBuilder's operator<< may be 53 // overloaded for custom types. 54 55 #include <optional> 56 #include <string_view> 57 #include <type_traits> 58 59 #include "pw_result/result.h" 60 #include "pw_span/span.h" 61 #include "pw_status/status.h" 62 #include "pw_status/status_with_size.h" 63 #include "pw_string/format.h" 64 #include "pw_string/internal/config.h" 65 #include "pw_string/type_to_string.h" 66 67 namespace pw { 68 69 template <typename T> 70 StatusWithSize ToString(const T& value, span<char> buffer); 71 72 namespace internal { 73 74 template <typename T> 75 struct is_std_optional : std::false_type {}; 76 77 template <typename T> 78 struct is_std_optional<std::optional<T>> : std::true_type{}; 79 80 template <typename, typename = void> 81 constexpr bool is_iterable = false; 82 83 template <typename T> 84 constexpr bool is_iterable<T, 85 std::void_t<decltype(std::declval<T>().begin()), 86 decltype(std::declval<T>().end())>> = 87 true; 88 89 template <typename BeginType, typename EndType> 90 inline StatusWithSize IterableToString(BeginType begin, 91 EndType end, 92 span<char> buffer) { 93 StatusWithSize s; 94 s.UpdateAndAdd(ToString("[", buffer)); 95 auto iter = begin; 96 if (iter != end && s.ok()) { 97 s.UpdateAndAdd(ToString(*iter, buffer.subspan(s.size()))); 98 ++iter; 99 } 100 while (iter != end && s.ok()) { 101 s.UpdateAndAdd(ToString(", ", buffer.subspan(s.size()))); 102 s.UpdateAndAdd(ToString(*iter, buffer.subspan(s.size()))); 103 ++iter; 104 } 105 s.UpdateAndAdd(ToString("]", buffer.subspan(s.size()))); 106 s.ZeroIfNotOk(); 107 return s; 108 } 109 110 } // namespace internal 111 112 // This function provides string printing numeric types, enums, and anything 113 // that convertible to a std::string_view, such as std::string. 114 template <typename T> 115 StatusWithSize ToString(const T& value, span<char> buffer) { 116 if constexpr (std::is_same_v<std::remove_cv_t<T>, bool>) { 117 return string::BoolToString(value, buffer); 118 } else if constexpr (std::is_same_v<std::remove_cv_t<T>, char>) { 119 return string::Copy(std::string_view(&value, 1), buffer); 120 } else if constexpr (std::is_integral_v<T>) { 121 return string::IntToString(value, buffer); 122 } else if constexpr (std::is_enum_v<T>) { 123 return string::IntToString(std::underlying_type_t<T>(value), buffer); 124 } else if constexpr (std::is_floating_point_v<T>) { 125 if constexpr (string::internal::config::kEnableDecimalFloatExpansion) { 126 // TODO(hepler): Look into using the float overload of std::to_chars when 127 // it is available. 128 return string::Format(buffer, "%.3f", value); 129 } else { 130 return string::FloatAsIntToString(static_cast<float>(value), buffer); 131 } 132 } else if constexpr (std::is_convertible_v<T, std::string_view>) { 133 return string::CopyStringOrNull(value, buffer); 134 } else if constexpr (std::is_pointer_v<std::remove_cv_t<T>> || 135 std::is_null_pointer_v<T>) { 136 return string::PointerToString(value, buffer); 137 } else if constexpr (internal::is_std_optional<std::remove_cv_t<T>>::value) { 138 if (value.has_value()) { 139 // NOTE: `*value`'s `ToString` is not wrapped for simplicity in the 140 // output. 141 // 142 // This is simpler, but may cause confusion in the rare case that folks 143 // are comparing nested optionals. For example, 144 // std::optional(std::nullopt) != std::nullopt will display as 145 // `std::nullopt != std::nullopt`. 146 return ToString(*value, buffer); 147 } else { 148 return ToString(std::nullopt, buffer); 149 } 150 } else if constexpr (std::is_same_v<std::remove_cv_t<T>, std::nullopt_t>) { 151 return string::CopyStringOrNull("std::nullopt", buffer); 152 } else if constexpr (internal::is_iterable<T>) { 153 return internal::IterableToString(value.begin(), value.end(), buffer); 154 } else { 155 // By default, no definition of UnknownTypeToString is provided. 156 return string::UnknownTypeToString(value, buffer); 157 } 158 } 159 160 // ToString overloads for Pigweed types. To override ToString for a custom type, 161 // specialize the ToString template function. 162 inline StatusWithSize ToString(Status status, span<char> buffer) { 163 return string::Copy(status.str(), buffer); 164 } 165 166 inline StatusWithSize ToString(pw_Status status, span<char> buffer) { 167 return ToString(Status(status), buffer); 168 } 169 170 template <typename T> 171 inline StatusWithSize ToString(const Result<T>& result, span<char> buffer) { 172 if (result.ok()) { 173 StatusWithSize s; 174 s.UpdateAndAdd(ToString("Ok(", buffer)); 175 s.UpdateAndAdd(ToString(*result, buffer.subspan(s.size()))); 176 s.UpdateAndAdd(ToString(")", buffer.subspan(s.size()))); 177 s.ZeroIfNotOk(); 178 return s; 179 } 180 return ToString(result.status(), buffer); 181 } 182 183 inline StatusWithSize ToString(std::byte byte, span<char> buffer) { 184 return string::IntToHexString(static_cast<unsigned>(byte), buffer, 2); 185 } 186 187 } // namespace pw 188