Commit 3875136d authored by Dominik Charousset's avatar Dominik Charousset

Improve stringification_inspector implementation

- Remove unnecessary `deconst`
- Move more code to the `.cpp` file
- Properly escape tab and newline characters
- Print timespans in the highest resolution possible
parent 69bd2505
...@@ -18,47 +18,37 @@ ...@@ -18,47 +18,37 @@
#pragma once #pragma once
#include <array>
#include <tuple>
#include <string> #include <string>
#include <utility> #include <tuple>
#include <functional>
#include <type_traits>
#include "caf/atom.hpp"
#include "caf/detail/apply_args.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/stringification_inspector.hpp" #include "caf/detail/stringification_inspector.hpp"
namespace caf { 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 /// Unrolls collections such as vectors/maps, decomposes
/// tuples/pairs/arrays, auto-escapes strings and calls /// tuples/pairs/arrays, auto-escapes strings and calls
/// `to_string` for user-defined types via argument-dependent /// `to_string` for user-defined types via argument-dependent
/// loopkup (ADL). Any user-defined type that does not /// loopkup (ADL). Any user-defined type that does not
/// provide a `to_string` is mapped to `<unprintable>`. /// 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...))`. /// Convenience function for `deep_to_string(std::forward_as_tuple(xs...))`.
template <class... Ts> template <class... Ts>
std::string deep_to_string_as_tuple(Ts&&... xs) { std::string deep_to_string_as_tuple(const Ts&... xs) {
return deep_to_string(std::forward_as_tuple(std::forward<Ts>(xs)...)); return deep_to_string(std::forward_as_tuple(xs...));
} }
} // namespace caf } // namespace caf
...@@ -19,16 +19,14 @@ ...@@ -19,16 +19,14 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <functional>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <vector>
#include "caf/atom.hpp"
#include "caf/detail/append_hex.hpp" #include "caf/detail/append_hex.hpp"
#include "caf/detail/apply_args.hpp" #include "caf/detail/apply_args.hpp"
#include "caf/detail/int_list.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
#include "caf/error.hpp" #include "caf/fwd.hpp"
#include "caf/meta/annotation.hpp" #include "caf/meta/annotation.hpp"
#include "caf/meta/hex_formatted.hpp" #include "caf/meta/hex_formatted.hpp"
#include "caf/meta/omittable.hpp" #include "caf/meta/omittable.hpp"
...@@ -68,84 +66,85 @@ public: ...@@ -68,84 +66,85 @@ public:
/// Prints a separator to the result string. /// Prints a separator to the result string.
void sep(); void sep();
inline void traverse() noexcept { void consume(atom_value x);
// end of recursion
}
void consume(atom_value& x);
void consume(string_view str); 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(bool x);
void consume(std::chrono::time_point<Clock, Duration>& x) {
timestamp tmp{std::chrono::duration_cast<timespan>(x.time_since_epoch())};
consume(tmp);
}
template <class Rep, class Period> void consume(const void* ptr);
void consume(std::chrono::duration<Rep, Period>& x) {
auto tmp = std::chrono::duration_cast<timespan>(x); void consume(const char* cstr);
consume(tmp);
} void consume(float x);
void consume(double x);
void consume(long double x);
inline void consume(bool& x) { void consume(int64_t x);
result_ += x ? "true" : "false";
void consume(uint64_t x);
template <class T>
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value>
consume(T x) {
consume(static_cast<int64_t>(x));
} }
inline void consume(const char* cstr) { template <class T>
if (cstr == nullptr) { enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>
result_ += "null"; consume(T x) {
} else { consume(static_cast<uint64_t>(x));
string_view tmp{cstr, strlen(cstr)};
consume(tmp);
}
} }
inline void consume(char* cstr) { template <class Clock, class Duration>
consume(const_cast<const char*>(cstr)); 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> template <class Rep, class Period>
enable_if_tt<std::is_arithmetic<T>> consume(T& x) { void consume(std::chrono::duration<Rep, Period> x) {
result_ += std::to_string(x); auto tmp = std::chrono::duration_cast<timespan>(x);
consume(tmp);
} }
// unwrap std::ref // Unwrap std::ref.
template <class T> template <class T>
void consume(std::reference_wrapper<T>& x) { void consume(std::reference_wrapper<T> x) {
return consume(x.get()); return consume(x.get());
} }
/// Picks up user-defined `to_string` functions. /// Picks up user-defined `to_string` functions.
template <class T> template <class T>
enable_if_t<!std::is_pointer<T>::value && has_to_string<T>::value> enable_if_t<!std::is_pointer<T>::value && has_to_string<T>::value>
consume(T& x) { consume(const T& x) {
result_ += to_string(x); result_ += to_string(x);
} }
/// Delegates to `inspect(*this, x)` if available and `T` /// Delegates to `inspect(*this, x)` if available and `T` does not provide
/// does not provide a `to_string`. /// a `to_string` overload.
template <class T> template <class T>
enable_if_t< enable_if_t<is_inspectable<stringification_inspector, T>::value
is_inspectable<stringification_inspector, T>::value && !has_to_string<T>::value>
&& !has_to_string<T>::value> consume(const T& x) {
consume(T& x) { inspect(*this, const_cast<T&>(x));
inspect(*this, x);
} }
template <class F, class S> template <class F, class S>
void consume(std::pair<F, S>& x) { void consume(const std::pair<F, S>& x) {
result_ += '('; result_ += '(';
traverse(deconst(x.first), deconst(x.second)); traverse(x.first, x.second);
result_ += ')'; result_ += ')';
} }
template <class... Ts> template <class... Ts>
void consume(std::tuple<Ts...>& x) { void consume(const std::tuple<Ts...>& x) {
result_ += '('; result_ += '(';
apply_args(*this, get_indices(x), x); apply_args(*this, get_indices(x), x);
result_ += ')'; result_ += ')';
...@@ -154,15 +153,14 @@ public: ...@@ -154,15 +153,14 @@ public:
template <class T> template <class T>
enable_if_t<is_map_like<T>::value enable_if_t<is_map_like<T>::value
&& !is_inspectable<stringification_inspector, T>::value && !is_inspectable<stringification_inspector, T>::value
&& !std::is_convertible<T, string_view>::value
&& !has_to_string<T>::value> && !has_to_string<T>::value>
consume(T& xs) { consume(const T& xs) {
result_ += '{'; result_ += '{';
for (const auto& kvp : xs) { for (const auto& kvp : xs) {
sep(); sep();
consume(deconst(kvp.first)); consume(kvp.first);
result_ += " = "; result_ += " = ";
consume(deconst(kvp.second)); consume(kvp.second);
} }
result_ += '}'; result_ += '}';
} }
...@@ -171,14 +169,15 @@ public: ...@@ -171,14 +169,15 @@ public:
enable_if_t<is_iterable<T>::value && !is_map_like<T>::value enable_if_t<is_iterable<T>::value && !is_map_like<T>::value
&& !is_inspectable<stringification_inspector, T>::value && !is_inspectable<stringification_inspector, T>::value
&& !std::is_convertible<T, string_view>::value && !std::is_convertible<T, string_view>::value
&& !is_inspectable<stringification_inspector, T>::value
&& !has_to_string<T>::value> && !has_to_string<T>::value>
consume(T& xs) { consume(const T& xs) {
result_ += '['; result_ += '[';
// use a hand-written for loop instead of for-each to avoid // use a hand-written for loop instead of for-each to avoid
// range-loop-analysis warnings when using this function with vector<bool> // range-loop-analysis warnings when using this function with vector<bool>
for (auto i = xs.begin(); i != xs.end(); ++i) { for (auto i = xs.begin(); i != xs.end(); ++i) {
sep(); sep();
consume(deconst(*i)); consume(*i);
} }
result_ += ']'; result_ += ']';
} }
...@@ -195,28 +194,30 @@ public: ...@@ -195,28 +194,30 @@ public:
} }
template <class T> template <class T>
void consume(T* xs, size_t n) { void consume(const T* xs, size_t n) {
result_ += '('; result_ += '(';
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
sep(); sep();
consume(deconst(xs[i])); consume(xs[i]);
} }
result_ += ')'; result_ += ')';
} }
template <class T, size_t S> template <class T, size_t S>
void consume(std::array<T, S>& xs) { void consume(const std::array<T, S>& xs) {
return consume(xs.data(), S); return consume(xs.data(), S);
} }
template <class T, size_t S> template <class T, size_t S>
void consume(T (&xs)[S]) { void consume(const T (&xs)[S]) {
return consume(xs, S); return consume(xs, S);
} }
template <class T> template <class T>
enable_if_t<!std::is_same<decay_t<T>, void>::value> enable_if_t<
consume(T*& ptr) { std::is_pointer<T>::value
&& !std::is_same<void, typename std::remove_pointer<T>::type>::value>
consume(const T ptr) {
if (ptr) { if (ptr) {
result_ += '*'; result_ += '*';
consume(*ptr); consume(*ptr);
...@@ -225,68 +226,22 @@ public: ...@@ -225,68 +226,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>`. /// Fallback printing `<unprintable>`.
template <class T> template <class T>
enable_if_t< enable_if_t<!is_iterable<T>::value && !has_peek_all<T>::value
!is_iterable<T>::value && !std::is_pointer<T>::value
&& !has_peek_all<T>::value && !is_inspectable<stringification_inspector, T>::value
&& !std::is_pointer<T>::value && !std::is_arithmetic<T>::value
&& !is_inspectable<stringification_inspector, T>::value && !std::is_convertible<T, string_view>::value
&& !std::is_arithmetic<T>::value && !has_to_string<T>::value>
&& !std::is_convertible<T, string_view>::value consume(const T&) {
&& !has_to_string<T>::value>
consume(T&) {
result_ += "<unprintable>"; result_ += "<unprintable>";
} }
void traverse() {
// end of recursion
}
template <class T, class... Ts> template <class T, class... Ts>
void traverse(const meta::hex_formatted_t&, const T& x, const Ts&... xs) { void traverse(const meta::hex_formatted_t&, const T& x, const Ts&... xs) {
sep(); sep();
...@@ -336,7 +291,7 @@ public: ...@@ -336,7 +291,7 @@ public:
enable_if_t<!meta::is_annotation<T>::value && !is_callable<T>::value> enable_if_t<!meta::is_annotation<T>::value && !is_callable<T>::value>
traverse(const T& x, const Ts&... xs) { traverse(const T& x, const Ts&... xs) {
sep(); sep();
consume(deconst(x)); consume(x);
traverse(xs...); traverse(xs...);
} }
...@@ -349,14 +304,8 @@ public: ...@@ -349,14 +304,8 @@ public:
} }
private: private:
template <class T>
T& deconst(const T& x) {
return const_cast<T&>(x);
}
std::string& result_; std::string& result_;
}; };
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
...@@ -18,8 +18,35 @@ ...@@ -18,8 +18,35 @@
#include "caf/detail/stringification_inspector.hpp" #include "caf/detail/stringification_inspector.hpp"
#include <algorithm>
#include <ctime> #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 caf {
namespace detail { namespace detail {
...@@ -36,7 +63,7 @@ void stringification_inspector::sep() { ...@@ -36,7 +63,7 @@ void stringification_inspector::sep() {
} }
} }
void stringification_inspector::consume(atom_value& x) { void stringification_inspector::consume(atom_value x) {
result_ += '\''; result_ += '\'';
result_ += to_string(x); result_ += to_string(x);
result_ += '\''; result_ += '\'';
...@@ -54,42 +81,54 @@ void stringification_inspector::consume(string_view str) { ...@@ -54,42 +81,54 @@ void stringification_inspector::consume(string_view str) {
} }
// Escape string. // Escape string.
result_ += '"'; result_ += '"';
for (char c : str) { for (char c : str)
switch (c) { escape(result_, c);
default:
result_ += c;
break;
case '\\':
result_ += R"(\\)";
break;
case '"':
result_ += R"(\")";
break;
}
}
result_ += '"'; result_ += '"';
} }
void stringification_inspector::consume(timespan& x) { void stringification_inspector::consume(timespan x) {
auto count = x.count(); auto ns = x.count();
auto res = [&](const char* suffix) { if (ns % 1000 > 0) {
result_ += std::to_string(count); consume(ns);
result_ += suffix; result_ += "ns";
}; return;
// 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. auto us = ns / 1000;
if (count % 60 != 0) if (us % 1000 > 0) {
return res("s"); consume(us);
count /= 60; result_ += "us";
return res("min"); 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]; char buf[64];
auto y = std::chrono::time_point_cast<timestamp::clock::duration>(x); auto y = std::chrono::time_point_cast<timestamp::clock::duration>(x);
auto z = timestamp::clock::to_time_t(y); auto z = timestamp::clock::to_time_t(y);
...@@ -104,5 +143,72 @@ void stringification_inspector::consume(timestamp& x) { ...@@ -104,5 +143,72 @@ void stringification_inspector::consume(timestamp& x) {
result_ += frac; 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(float x) {
result_ += std::to_string(x);
}
void stringification_inspector::consume(double x) {
result_ += std::to_string(x);
}
void stringification_inspector::consume(long double x) {
result_ += std::to_string(x);
}
void stringification_inspector::consume(int64_t x) {
if (x >= 0)
return consume(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(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 detail
} // namespace caf } // namespace caf
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