• 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 
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