Unverified Commit 68641365 authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #899

Improve stringification_inspector implementation
parents 5dff9fbc 3f1a546f
......@@ -18,47 +18,37 @@
#pragma once
#include <array>
#include <tuple>
#include <string>
#include <utility>
#include <functional>
#include <type_traits>
#include <tuple>
#include "caf/atom.hpp"
#include "caf/detail/apply_args.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/stringification_inspector.hpp"
namespace caf {
class deep_to_string_t {
public:
constexpr deep_to_string_t() {
// nop
}
template <class... Ts>
std::string operator()(Ts&&... xs) const {
std::string result;
detail::stringification_inspector f{result};
f(std::forward<Ts>(xs)...);
return result;
}
};
/// Unrolls collections such as vectors/maps, decomposes
/// tuples/pairs/arrays, auto-escapes strings and calls
/// `to_string` for user-defined types via argument-dependent
/// loopkup (ADL). Any user-defined type that does not
/// provide a `to_string` is mapped to `<unprintable>`.
constexpr deep_to_string_t deep_to_string = deep_to_string_t{};
template <class... Ts>
std::string deep_to_string(const Ts&... xs) {
std::string result;
detail::stringification_inspector f{result};
f(xs...);
return result;
}
struct deep_to_string_t {
template <class... Ts>
std::string operator()(const Ts&... xs) const {
return deep_to_string(xs...);
}
};
/// Convenience function for `deep_to_string(std::forward_as_tuple(xs...))`.
template <class... Ts>
std::string deep_to_string_as_tuple(Ts&&... xs) {
return deep_to_string(std::forward_as_tuple(std::forward<Ts>(xs)...));
std::string deep_to_string_as_tuple(const Ts&... xs) {
return deep_to_string(std::forward_as_tuple(xs...));
}
} // namespace caf
......@@ -46,11 +46,11 @@ void read_timespan(state<Iterator, Sentinel>& ps, Consumer&& consumer,
struct interim_consumer {
using value_type = int64_t;
void value(int64_t y) {
void value(value_type y) {
x = y;
}
int64_t x = 0;
value_type x = 0;
};
interim_consumer ic;
timespan result;
......
......@@ -19,16 +19,15 @@
#pragma once
#include <chrono>
#include <functional>
#include <string>
#include <type_traits>
#include <vector>
#include "caf/atom.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/apply_args.hpp"
#include "caf/detail/int_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/meta/annotation.hpp"
#include "caf/meta/hex_formatted.hpp"
#include "caf/meta/omittable.hpp"
......@@ -68,84 +67,82 @@ public:
/// Prints a separator to the result string.
void sep();
inline void traverse() noexcept {
// end of recursion
}
void consume(atom_value& x);
void consume(atom_value x);
void consume(string_view str);
void consume(timespan& x);
void consume(timespan x);
void consume(timestamp& x);
void consume(timestamp x);
template <class Clock, class Duration>
void consume(std::chrono::time_point<Clock, Duration>& x) {
timestamp tmp{std::chrono::duration_cast<timespan>(x.time_since_epoch())};
consume(tmp);
}
void consume(bool x);
template <class Rep, class Period>
void consume(std::chrono::duration<Rep, Period>& x) {
auto tmp = std::chrono::duration_cast<timespan>(x);
consume(tmp);
}
void consume(const void* ptr);
void consume(const char* cstr);
void consume(const std::vector<bool>& xs);
inline void consume(bool& x) {
result_ += x ? "true" : "false";
template <class T>
enable_if_t<std::is_floating_point<T>::value> consume(T x) {
result_ += std::to_string(x);
}
inline void consume(const char* cstr) {
if (cstr == nullptr) {
result_ += "null";
} else {
string_view tmp{cstr, strlen(cstr)};
consume(tmp);
template <class T>
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value>
consume(T x) {
consume_int(static_cast<int64_t>(x));
}
template <class T>
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>
consume(T x) {
consume_int(static_cast<uint64_t>(x));
}
inline void consume(char* cstr) {
consume(const_cast<const char*>(cstr));
template <class Clock, class Duration>
void consume(std::chrono::time_point<Clock, Duration> x) {
timestamp tmp{std::chrono::duration_cast<timespan>(x.time_since_epoch())};
consume(tmp);
}
template <class T>
enable_if_tt<std::is_arithmetic<T>> consume(T& x) {
result_ += std::to_string(x);
template <class Rep, class Period>
void consume(std::chrono::duration<Rep, Period> x) {
auto tmp = std::chrono::duration_cast<timespan>(x);
consume(tmp);
}
// unwrap std::ref
// Unwrap std::ref.
template <class T>
void consume(std::reference_wrapper<T>& x) {
void consume(std::reference_wrapper<T> x) {
return consume(x.get());
}
/// Picks up user-defined `to_string` functions.
template <class T>
enable_if_t<!std::is_pointer<T>::value && has_to_string<T>::value>
consume(T& x) {
consume(const T& x) {
result_ += to_string(x);
}
/// Delegates to `inspect(*this, x)` if available and `T`
/// does not provide a `to_string`.
/// Delegates to `inspect(*this, x)` if available and `T` does not provide
/// a `to_string` overload.
template <class T>
enable_if_t<
is_inspectable<stringification_inspector, T>::value
enable_if_t<is_inspectable<stringification_inspector, T>::value
&& !has_to_string<T>::value>
consume(T& x) {
inspect(*this, x);
consume(const T& x) {
inspect(*this, const_cast<T&>(x));
}
template <class F, class S>
void consume(std::pair<F, S>& x) {
void consume(const std::pair<F, S>& x) {
result_ += '(';
traverse(deconst(x.first), deconst(x.second));
traverse(x.first, x.second);
result_ += ')';
}
template <class... Ts>
void consume(std::tuple<Ts...>& x) {
void consume(const std::tuple<Ts...>& x) {
result_ += '(';
apply_args(*this, get_indices(x), x);
result_ += ')';
......@@ -154,33 +151,40 @@ public:
template <class T>
enable_if_t<is_map_like<T>::value
&& !is_inspectable<stringification_inspector, T>::value
&& !std::is_convertible<T, string_view>::value
&& !has_to_string<T>::value>
consume(T& xs) {
consume(const T& xs) {
result_ += '{';
for (const auto& kvp : xs) {
sep();
consume(deconst(kvp.first));
consume(kvp.first);
result_ += " = ";
consume(deconst(kvp.second));
consume(kvp.second);
}
result_ += '}';
}
template <class Iterator>
void consume_range(Iterator first, Iterator last) {
result_ += '[';
while (first != last) {
sep();
consume(*first++);
}
result_ += ']';
}
template <class T>
enable_if_t<is_iterable<T>::value && !is_map_like<T>::value
&& !is_inspectable<stringification_inspector, T>::value
&& !std::is_convertible<T, string_view>::value
&& !is_inspectable<stringification_inspector, T>::value
&& !has_to_string<T>::value>
consume(T& xs) {
result_ += '[';
// use a hand-written for loop instead of for-each to avoid
// range-loop-analysis warnings when using this function with vector<bool>
for (auto i = xs.begin(); i != xs.end(); ++i) {
sep();
consume(deconst(*i));
consume(const T& xs) {
consume_range(xs.begin(), xs.end());
}
result_ += ']';
template <class T, size_t S>
void consume(const T (&xs)[S]) {
return consume_range(xs, xs + S);
}
template <class T>
......@@ -188,35 +192,17 @@ public:
&& !is_iterable<T>::value // pick begin()/end() over peek_all
&& !is_inspectable<stringification_inspector, T>::value
&& !has_to_string<T>::value>
consume(T& xs) {
consume(const T& xs) {
result_ += '[';
xs.peek_all(*this);
result_ += ']';
}
template <class T>
void consume(T* xs, size_t n) {
result_ += '(';
for (size_t i = 0; i < n; ++i) {
sep();
consume(deconst(xs[i]));
}
result_ += ')';
}
template <class T, size_t S>
void consume(std::array<T, S>& xs) {
return consume(xs.data(), S);
}
template <class T, size_t S>
void consume(T (&xs)[S]) {
return consume(xs, S);
}
template <class T>
enable_if_t<!std::is_same<decay_t<T>, void>::value>
consume(T*& ptr) {
enable_if_t<
std::is_pointer<T>::value
&& !std::is_same<void, typename std::remove_pointer<T>::type>::value>
consume(const T ptr) {
if (ptr) {
result_ += '*';
consume(*ptr);
......@@ -225,68 +211,22 @@ public:
}
}
inline void consume(const void* ptr) {
result_ += "0x";
auto int_val = reinterpret_cast<intptr_t>(ptr);
consume(int_val);
}
/// Print duration types with nanosecond resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::nano>& x) {
result_ += std::to_string(x.count());
result_ += "ns";
}
/// Print duration types with microsecond resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::micro>& x) {
result_ += std::to_string(x.count());
result_ += "us";
}
/// Print duration types with millisecond resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::milli>& x) {
result_ += std::to_string(x.count());
result_ += "ms";
}
/// Print duration types with second resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::ratio<1>>& x) {
result_ += std::to_string(x.count());
result_ += "s";
}
/// Print duration types with minute resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::ratio<60>>& x) {
result_ += std::to_string(x.count());
result_ += "min";
}
/// Print duration types with hour resolution.
template <class Rep>
void consume(std::chrono::duration<Rep, std::ratio<3600>>& x) {
result_ += std::to_string(x.count());
result_ += "h";
}
/// Fallback printing `<unprintable>`.
template <class T>
enable_if_t<
!is_iterable<T>::value
&& !has_peek_all<T>::value
enable_if_t<!is_iterable<T>::value && !has_peek_all<T>::value
&& !std::is_pointer<T>::value
&& !is_inspectable<stringification_inspector, T>::value
&& !std::is_arithmetic<T>::value
&& !std::is_convertible<T, string_view>::value
&& !has_to_string<T>::value>
consume(T&) {
consume(const T&) {
result_ += "<unprintable>";
}
void traverse() {
// end of recursion
}
template <class T, class... Ts>
void traverse(const meta::hex_formatted_t&, const T& x, const Ts&... xs) {
sep();
......@@ -336,7 +276,7 @@ public:
enable_if_t<!meta::is_annotation<T>::value && !is_callable<T>::value>
traverse(const T& x, const Ts&... xs) {
sep();
consume(deconst(x));
consume(x);
traverse(xs...);
}
......@@ -349,14 +289,12 @@ public:
}
private:
template <class T>
T& deconst(const T& x) {
return const_cast<T&>(x);
}
void consume_int(int64_t x);
void consume_int(uint64_t x);
std::string& result_;
};
} // namespace detail
} // namespace caf
......@@ -336,7 +336,7 @@ void print(const config_value::dictionary& xs, indentation indent) {
cout << indent << kvp.first << " = " << to_string(kvp.second) << '\n';
}
}
};
}
} // namespace <anonymous>
......
......@@ -18,8 +18,35 @@
#include "caf/detail/stringification_inspector.hpp"
#include <algorithm>
#include <ctime>
#include "caf/atom.hpp"
namespace {
void escape(std::string& result, char c) {
switch (c) {
default:
result += c;
break;
case '\n':
result += R"(\n)";
break;
case '\t':
result += R"(\t)";
break;
case '\\':
result += R"(\\)";
break;
case '"':
result += R"(\")";
break;
}
}
} // namespace
namespace caf {
namespace detail {
......@@ -36,7 +63,7 @@ void stringification_inspector::sep() {
}
}
void stringification_inspector::consume(atom_value& x) {
void stringification_inspector::consume(atom_value x) {
result_ += '\'';
result_ += to_string(x);
result_ += '\'';
......@@ -54,42 +81,54 @@ void stringification_inspector::consume(string_view str) {
}
// Escape string.
result_ += '"';
for (char c : str) {
switch (c) {
default:
result_ += c;
break;
case '\\':
result_ += R"(\\)";
break;
case '"':
result_ += R"(\")";
break;
}
}
for (char c : str)
escape(result_, c);
result_ += '"';
}
void stringification_inspector::consume(timespan& x) {
auto count = x.count();
auto res = [&](const char* suffix) {
result_ += std::to_string(count);
result_ += suffix;
};
// Check whether it's nano-, micro-, or milliseconds.
for (auto suffix : {"ns", "us", "ms"}) {
if (count % 1000 != 0)
return res(suffix);
count /= 1000;
}
// After the loop we only need to differentiate between seconds and minutes.
if (count % 60 != 0)
return res("s");
count /= 60;
return res("min");
void stringification_inspector::consume(timespan x) {
auto ns = x.count();
if (ns % 1000 > 0) {
consume(ns);
result_ += "ns";
return;
}
auto us = ns / 1000;
if (us % 1000 > 0) {
consume(us);
result_ += "us";
return;
}
auto ms = us / 1000;
if (ms % 1000 > 0) {
consume(ms);
result_ += "ms";
return;
}
auto s = ms / 1000;
if (s % 60 > 0) {
consume(s);
result_ += "s";
return;
}
auto min = s / 60;
if (min % 60 > 0) {
consume(min);
result_ += "min";
return;
}
auto h = min / 60;
if (min % 24 > 0) {
consume(h);
result_ += "h";
return;
}
auto d = h / 24;
consume(d);
result_ += "d";
}
void stringification_inspector::consume(timestamp& x) {
void stringification_inspector::consume(timestamp x) {
char buf[64];
auto y = std::chrono::time_point_cast<timestamp::clock::duration>(x);
auto z = timestamp::clock::to_time_t(y);
......@@ -104,5 +143,69 @@ void stringification_inspector::consume(timestamp& x) {
result_ += frac;
}
void stringification_inspector::consume(bool x) {
result_ += x ? "true" : "false";
}
void stringification_inspector::consume(const void* ptr) {
result_ += "0x";
consume(reinterpret_cast<intptr_t>(ptr));
}
void stringification_inspector::consume(const char* cstr) {
if (cstr == nullptr) {
result_ += "<null>";
return;
}
if (cstr[0] == '\0') {
result_ += R"("")";
return;
}
if (cstr[0] == '"') {
// Assume an already escaped string.
result_ += cstr;
return;
}
// Escape string.
result_ += '"';
for (char c = *cstr++; c != '\0'; c = *cstr++)
escape(result_, c);
result_ += '"';
}
void stringification_inspector::consume(const std::vector<bool>& xs) {
result_ += '[';
for (bool x : xs) {
sep();
consume(x);
}
result_ += ']';
}
void stringification_inspector::consume_int(int64_t x) {
if (x >= 0)
return consume_int(static_cast<uint64_t>(x));
result_ += '-';
auto begin = result_.size();
result_ += -(x % 10) + '0';
x /= 10;
while (x != 0) {
result_ += -(x % 10) + '0';
x /= 10;
}
std::reverse(result_.begin() + begin, result_.end());
}
void stringification_inspector::consume_int(uint64_t x) {
auto begin = result_.size();
result_ += (x % 10) + '0';
x /= 10;
while (x != 0) {
result_ += (x % 10) + '0';
x /= 10;
}
std::reverse(result_.begin() + begin, result_.end());
}
} // namespace detail
} // namespace caf
......@@ -32,16 +32,38 @@ void foobar() {
} // namespace <anonymous>
#define CHECK_DEEP_TO_STRING(val, str) CAF_CHECK_EQUAL(deep_to_string(val), str)
CAF_TEST(timespans) {
CAF_CHECK_EQUAL(deep_to_string(timespan{1}), "1ns");
CAF_CHECK_EQUAL(deep_to_string(timespan{1000}), "1us");
CAF_CHECK_EQUAL(deep_to_string(timespan{1000000}), "1ms");
CAF_CHECK_EQUAL(deep_to_string(timespan{1000000000}), "1s");
CAF_CHECK_EQUAL(deep_to_string(timespan{60000000000}), "1min");
CHECK_DEEP_TO_STRING(timespan{1}, "1ns");
CHECK_DEEP_TO_STRING(timespan{1000}, "1us");
CHECK_DEEP_TO_STRING(timespan{1000000}, "1ms");
CHECK_DEEP_TO_STRING(timespan{1000000000}, "1s");
CHECK_DEEP_TO_STRING(timespan{60000000000}, "1min");
}
CAF_TEST(integer lists) {
int carray[] = {1, 2, 3, 4};
using array_type = std::array<int, 4>;
CHECK_DEEP_TO_STRING(std::list<int>({1, 2, 3, 4}), "[1, 2, 3, 4]");
CHECK_DEEP_TO_STRING(std::vector<int>({1, 2, 3, 4}), "[1, 2, 3, 4]");
CHECK_DEEP_TO_STRING(std::set<int>({1, 2, 3, 4}), "[1, 2, 3, 4]");
CHECK_DEEP_TO_STRING(array_type({{1, 2, 3, 4}}), "[1, 2, 3, 4]");
CHECK_DEEP_TO_STRING(carray, "[1, 2, 3, 4]");
}
CAF_TEST(boolean lists) {
bool carray[] = {false, true};
using array_type = std::array<bool, 2>;
CHECK_DEEP_TO_STRING(std::list<bool>({false, true}), "[false, true]");
CHECK_DEEP_TO_STRING(std::vector<bool>({false, true}), "[false, true]");
CHECK_DEEP_TO_STRING(std::set<bool>({false, true}), "[false, true]");
CHECK_DEEP_TO_STRING(array_type({{false, true}}), "[false, true]");
CHECK_DEEP_TO_STRING(carray, "[false, true]");
}
CAF_TEST(pointers) {
auto i = 42;
CAF_CHECK_EQUAL(deep_to_string(&i), "*42");
CAF_CHECK_EQUAL(deep_to_string(foobar), "<fun>");
CHECK_DEEP_TO_STRING(&i, "*42");
CHECK_DEEP_TO_STRING(foobar, "<fun>");
}
......@@ -272,10 +272,10 @@ CAF_TEST(tuples_to_string) {
}
CAF_TEST(arrays_to_string) {
CAF_CHECK_EQUAL(msg_as_string(s1{}), "((10, 20, 30))");
CAF_CHECK_EQUAL(msg_as_string(s1{}), "([10, 20, 30])");
auto msg2 = make_message(s2{});
s2 tmp;
tmp.value[0][1] = 100;
CAF_CHECK_EQUAL(to_string(msg2), "(((1, 10), (2, 20), (3, 30), (4, 40)))");
CAF_CHECK_EQUAL(msg_as_string(s3{}), "((1, 2, 3, 4))");
CAF_CHECK_EQUAL(to_string(msg2), "([[1, 10], [2, 20], [3, 30], [4, 40]])");
CAF_CHECK_EQUAL(msg_as_string(s3{}), "([1, 2, 3, 4])");
}
......@@ -554,7 +554,7 @@ SERIALIZATION_TEST(bool_vector_size_0) {
SERIALIZATION_TEST(bool_vector_size_1) {
std::vector<bool> xs{true};
CAF_CHECK_EQUAL(deep_to_string(xs), "[1]");
CAF_CHECK_EQUAL(deep_to_string(xs), "[true]");
CAF_CHECK_EQUAL(xs, roundtrip(xs));
CAF_CHECK_EQUAL(xs, msg_roundtrip(xs));
}
......@@ -563,11 +563,14 @@ SERIALIZATION_TEST(bool_vector_size_63) {
std::vector<bool> xs;
for (int i = 0; i < 63; ++i)
xs.push_back(i % 3 == 0);
CAF_CHECK_EQUAL(deep_to_string(xs),
"[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, "
"0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, "
"1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, "
"0, 1, 0, 0]");
CAF_CHECK_EQUAL(
deep_to_string(xs),
"[true, false, false, true, false, false, true, false, false, true, false, "
"false, true, false, false, true, false, false, true, false, false, true, "
"false, false, true, false, false, true, false, false, true, false, false, "
"true, false, false, true, false, false, true, false, false, true, false, "
"false, true, false, false, true, false, false, true, false, false, true, "
"false, false, true, false, false, true, false, false]");
CAF_CHECK_EQUAL(xs, roundtrip(xs));
CAF_CHECK_EQUAL(xs, msg_roundtrip(xs));
}
......@@ -577,10 +580,14 @@ SERIALIZATION_TEST(bool_vector_size_64) {
for (int i = 0; i < 64; ++i)
xs.push_back(i % 5 == 0);
CAF_CHECK_EQUAL(deep_to_string(xs),
"[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, "
"0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, "
"0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, "
"0, 1, 0, 0, 0]");
"[true, false, false, false, false, true, false, false, "
"false, false, true, false, false, false, false, true, "
"false, false, false, false, true, false, false, false, "
"false, true, false, false, false, false, true, false, "
"false, false, false, true, false, false, false, false, "
"true, false, false, false, false, true, false, false, "
"false, false, true, false, false, false, false, true, "
"false, false, false, false, true, false, false, false]");
CAF_CHECK_EQUAL(xs, roundtrip(xs));
CAF_CHECK_EQUAL(xs, msg_roundtrip(xs));
}
......@@ -589,11 +596,14 @@ SERIALIZATION_TEST(bool_vector_size_65) {
std::vector<bool> xs;
for (int i = 0; i < 65; ++i)
xs.push_back(!(i % 7 == 0));
CAF_CHECK_EQUAL(deep_to_string(xs),
"[0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, "
"1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, "
"1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, "
"1, 1, 1, 1, 0, 1]");
CAF_CHECK_EQUAL(
deep_to_string(xs),
"[false, true, true, true, true, true, true, false, true, true, true, "
"true, true, true, false, true, true, true, true, true, true, false, true, "
"true, true, true, true, true, false, true, true, true, true, true, true, "
"false, true, true, true, true, true, true, false, true, true, true, true, "
"true, true, false, true, true, true, true, true, true, false, true, true, "
"true, true, true, true, false, true]");
CAF_CHECK_EQUAL(xs, roundtrip(xs));
CAF_CHECK_EQUAL(xs, msg_roundtrip(xs));
}
......@@ -165,4 +165,4 @@ CAF_TEST(serialize and deserialize with std::vector<uint8_t>) {
CAF_CHECK_EQUAL(data_to_serialize, deserialized_data);
}
CAF_TEST_FIXTURE_SCOPE_END();
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment