1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4
5 #pragma once
6
7 #include "caf/none.hpp"
8 #include "caf/string_view.hpp"
9
10 #include <chrono>
11 #include <cstdint>
12 #include <ctime>
13 #include <limits>
14 #include <type_traits>
15
16 namespace caf::detail {
17
18 CAF_CORE_EXPORT
19 size_t print_timestamp(char* buf, size_t buf_size, time_t ts, size_t ms);
20
21 template <class Buffer>
print_escaped(Buffer & buf,string_view str)22 void print_escaped(Buffer& buf, string_view str) {
23 buf.push_back('"');
24 for (auto c : str) {
25 switch (c) {
26 default:
27 buf.push_back(c);
28 break;
29 case '\\':
30 buf.push_back('\\');
31 buf.push_back('\\');
32 break;
33 case '\b':
34 buf.push_back('\\');
35 buf.push_back('b');
36 break;
37 case '\f':
38 buf.push_back('\\');
39 buf.push_back('f');
40 break;
41 case '\n':
42 buf.push_back('\\');
43 buf.push_back('n');
44 break;
45 case '\r':
46 buf.push_back('\\');
47 buf.push_back('r');
48 break;
49 case '\t':
50 buf.push_back('\\');
51 buf.push_back('t');
52 break;
53 case '\v':
54 buf.push_back('\\');
55 buf.push_back('v');
56 break;
57 case '"':
58 buf.push_back('\\');
59 buf.push_back('"');
60 break;
61 }
62 }
63 buf.push_back('"');
64 }
65
66 template <class Buffer>
print_unescaped(Buffer & buf,string_view str)67 void print_unescaped(Buffer& buf, string_view str) {
68 buf.reserve(buf.size() + str.size());
69 auto i = str.begin();
70 auto e = str.end();
71 while (i != e) {
72 switch (*i) {
73 default:
74 buf.push_back(*i);
75 ++i;
76 break;
77 case '\\':
78 if (++i != e) {
79 switch (*i) {
80 case '"':
81 buf.push_back('"');
82 break;
83 case '\\':
84 buf.push_back('\\');
85 break;
86 case 'b':
87 buf.push_back('\b');
88 break;
89 case 'f':
90 buf.push_back('\f');
91 break;
92 case 'n':
93 buf.push_back('\n');
94 break;
95 case 'r':
96 buf.push_back('\r');
97 break;
98 case 't':
99 buf.push_back('\t');
100 break;
101 case 'v':
102 buf.push_back('\v');
103 break;
104 default:
105 buf.push_back('?');
106 }
107 ++i;
108 }
109 }
110 }
111 }
112
113 template <class Buffer>
print(Buffer & buf,none_t)114 void print(Buffer& buf, none_t) {
115 using namespace caf::literals;
116 auto str = "null"_sv;
117 buf.insert(buf.end(), str.begin(), str.end());
118 }
119
120 template <class Buffer>
print(Buffer & buf,bool x)121 void print(Buffer& buf, bool x) {
122 using namespace caf::literals;
123 auto str = x ? "true"_sv : "false"_sv;
124 buf.insert(buf.end(), str.begin(), str.end());
125 }
126
127 template <class Buffer, class T>
print(Buffer & buf,T x)128 std::enable_if_t<std::is_integral<T>::value> print(Buffer& buf, T x) {
129 // An integer can at most have 20 digits (UINT64_MAX).
130 char stack_buffer[24];
131 char* p = stack_buffer;
132 // Convert negative values into positives as necessary.
133 if constexpr (std::is_signed<T>::value) {
134 if (x == std::numeric_limits<T>::min()) {
135 using namespace caf::literals;
136 // The code below would fail for the smallest value, because this value
137 // has no positive counterpart. For example, an int8_t ranges from -128 to
138 // 127. Hence, an int8_t cannot represent `abs(-128)`.
139 string_view result;
140 if constexpr (sizeof(T) == 1) {
141 result = "-128"_sv;
142 } else if constexpr (sizeof(T) == 2) {
143 result = "-32768"_sv;
144 } else if constexpr (sizeof(T) == 4) {
145 result = "-2147483648"_sv;
146 } else {
147 static_assert(sizeof(T) == 8);
148 result = "-9223372036854775808"_sv;
149 }
150 buf.insert(buf.end(), result.begin(), result.end());
151 return;
152 }
153 if (x < 0) {
154 buf.push_back('-');
155 x = -x;
156 }
157 }
158 // Fill the buffer.
159 *p++ = static_cast<char>((x % 10) + '0');
160 x /= 10;
161 while (x != 0) {
162 *p++ = static_cast<char>((x % 10) + '0');
163 x /= 10;
164 }
165 // Now, the buffer holds the string representation in reverse order.
166 do {
167 buf.push_back(*--p);
168 } while (p != stack_buffer);
169 }
170
171 template <class Buffer, class T>
print(Buffer & buf,T x)172 std::enable_if_t<std::is_floating_point<T>::value> print(Buffer& buf, T x) {
173 // TODO: Check whether to_chars is available on supported compilers and
174 // re-implement using the new API as soon as possible.
175 auto str = std::to_string(x);
176 if (str.find('.') != std::string::npos) {
177 // Drop trailing zeros.
178 while (str.back() == '0')
179 str.pop_back();
180 // Drop trailing dot as well if we've removed all decimal places.
181 if (str.back() == '.')
182 str.pop_back();
183 }
184 buf.insert(buf.end(), str.begin(), str.end());
185 }
186
187 template <class Buffer, class Rep, class Period>
print(Buffer & buf,std::chrono::duration<Rep,Period> x)188 void print(Buffer& buf, std::chrono::duration<Rep, Period> x) {
189 using namespace caf::literals;
190 if (x.count() == 0) {
191 auto str = "0s"_sv;
192 buf.insert(buf.end(), str.begin(), str.end());
193 return;
194 }
195 auto try_print = [&buf](auto converted, string_view suffix) {
196 if (converted.count() < 1)
197 return false;
198 print(buf, converted.count());
199 buf.insert(buf.end(), suffix.begin(), suffix.end());
200 return true;
201 };
202 namespace sc = std::chrono;
203 using hours = sc::duration<double, std::ratio<3600>>;
204 using minutes = sc::duration<double, std::ratio<60>>;
205 using seconds = sc::duration<double>;
206 using milliseconds = sc::duration<double, std::milli>;
207 using microseconds = sc::duration<double, std::micro>;
208 if (try_print(sc::duration_cast<hours>(x), "h")
209 || try_print(sc::duration_cast<minutes>(x), "min")
210 || try_print(sc::duration_cast<seconds>(x), "s")
211 || try_print(sc::duration_cast<milliseconds>(x), "ms")
212 || try_print(sc::duration_cast<microseconds>(x), "us"))
213 return;
214 auto converted = sc::duration_cast<sc::nanoseconds>(x);
215 print(buf, converted.count());
216 auto suffix = "ns"_sv;
217 buf.insert(buf.end(), suffix.begin(), suffix.end());
218 }
219
220 template <class Buffer, class Duration>
print(Buffer & buf,std::chrono::time_point<std::chrono::system_clock,Duration> x)221 void print(Buffer& buf,
222 std::chrono::time_point<std::chrono::system_clock, Duration> x) {
223 namespace sc = std::chrono;
224 using clock_type = sc::system_clock;
225 using clock_timestamp = typename clock_type::time_point;
226 using clock_duration = typename clock_type::duration;
227 auto tse = x.time_since_epoch();
228 clock_timestamp as_sys_time{sc::duration_cast<clock_duration>(tse)};
229 auto secs = clock_type::to_time_t(as_sys_time);
230 auto msecs = sc::duration_cast<sc::milliseconds>(tse).count() % 1000;
231 // We print in ISO 8601 format, e.g., "2020-09-01T15:58:42.372". 32-Bytes are
232 // more than enough space.
233 char stack_buffer[32];
234 auto pos = print_timestamp(stack_buffer, 32, secs,
235 static_cast<size_t>(msecs));
236 buf.insert(buf.end(), stack_buffer, stack_buffer + pos);
237 }
238
239 } // namespace caf::detail
240