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