first very hacky poc

This commit is contained in:
Martin Rückl 2020-07-07 17:15:05 +02:00
parent 37a5ecdd2c
commit 5df04d4634
3 changed files with 232 additions and 0 deletions

146
include/fmt/structured.h Normal file
View File

@ -0,0 +1,146 @@
#ifndef FMT_STRUCTURED_H
#define FMT_STRUCTURED_H
#include <string>
#include <variant>
#include "format.h"
#include "ranges.h" // fixme dont rely on ranges?!
FMT_BEGIN_NAMESPACE
/**
* Structure that holds reflection information about type T.
* @tparam T
*/
template <typename T, typename Enable = void> struct reflection {
static constexpr bool available = false;
};
// fixme probably the reflection should provide accessors to the data instead of
// (name, value) pairs. i.e. NamedField may have an operator() that returns the
// value of a field given the object the field belongs to.
template <typename T> struct NamedField {
const std::string name;
const T value;
bool operator==(const NamedField<T>& other) const {
return value == other.value && name == other.name;
};
};
template <typename T> struct Extended { const T& value; };
// fixme: Would probably be better to visit all name field elements directly in
// the Extended formatter instead of relying on user space specialization
// mechanism
/**
* Format a field name/value pair as ".{name}={value}"
* @tparam T
*/
template <typename T> struct fmt::formatter<NamedField<T>> {
template <typename FormatContext>
auto format(NamedField<T> const& t, FormatContext& ctx) {
*ctx.out()++ = '.';
std::copy(t.name.begin(), t.name.end(), ctx.out());
*ctx.out()++ = '=';
if constexpr (reflection<T>::available) {
return fmt::formatter<Extended<T>>{}.format(t.value, ctx);
} else {
return fmt::formatter<T>{}.format(t.value, ctx);
}
}
};
/**
* Formats objects as they would be written using designated initializers.
*
* E.g. {:e} will output Outer{.a=1, b=2, .inner=Inner{.x=3, .y=4, .z=5}}
*
* @tparam T The object to format.
* @tparam C The character type used.
*/
// fixme remove T template parameter
template <typename T, typename C>
struct fmt::formatter<Extended<T>, C,
std::enable_if_t<reflection<T>::available, void>> {
template <typename ParseContext> constexpr auto parse(ParseContext& ctx) {
auto it = ctx.begin();
if (*it != '}') throw format_error("configuration not yet supported");
return it;
}
template <typename FormatContext>
auto format(T const& t, FormatContext& ctx) {
std::string name = reflection<T>::name();
std::copy(name.begin(), name.end(), ctx.out());
*ctx.out()++ = '{';
using base = formatter<decltype(fmt::join(reflection<T>::fields(t), ", "))>;
base{}.format(fmt::join(reflection<T>::fields(t), ", "), ctx);
*ctx.out()++ = '}';
return ctx.out();
}
};
// fixme remove this class and merge it with the fallback formatter in detail
// namespace
template <typename T, typename Char>
struct fmt::formatter<T, Char,
std::enable_if_t<reflection<T>::available, void>> {
using extended = fmt::formatter<Extended<T>>;
template <typename ParseContext> constexpr auto parse(ParseContext& ctx) {
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (*(it++) != 'e') {
throw format_error("invalid format");
}
_extended = extended{};
ctx.advance_to(it);
_extended.parse(ctx);
// Check if reached the end of the range:
if (it != end && *it != '}') {
throw format_error("invalid format");
}
// Return an iterator past the end of the parsed range:
return it;
}
template <typename FormatContext>
auto format(T const& t, FormatContext& ctx) {
return _extended.format(t, ctx);
}
private:
extended _extended;
};
namespace detail {
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<reflection<T>::available>> {
using extended = fmt::formatter<Extended<T>>;
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = formatter<basic_string_view<Char>, Char>::parse(ctx);
// fixme: not sure what to return, if the closing '}' remains (like required
// for formatter specialization)
// some internals will throw from ErrorHandler::on_error("invalid
// type specifier");
ctx.advance_to(it++); // fixme: also not sure
return it;
}
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) {
return extended{}.format(value, ctx);
}
};
} // namespace detail
FMT_END_NAMESPACE
#endif // FMT_STRUCTURED_H

View File

@ -103,6 +103,9 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif ()
add_fmt_test(locale-test)
add_fmt_test(ostream-test)
add_fmt_test(reflect-struct-test)
# currently this requires constexpr if, variadic templates and potentially std::variant
set_property(TARGET reflect-struct-test PROPERTY CXX_STANDARD 17)
add_fmt_test(compile-test)
add_fmt_test(printf-test)
add_fmt_test(custom-formatter-test)

View File

@ -0,0 +1,83 @@
// Formatting library for C++ - std::ostream support tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#define FMT_STRING_ALIAS 1
#include "fmt/format.h"
struct Inner {
int x;
double y;
std::string z;
};
struct Outer {
std::string a;
std::string b;
Inner inner;
};
// fixme: if this is enabled, then then fmt::format("{:e}", Outer{...}) fails,
// because internally formatter<Inner> will be used to parse the "{:e}"
// format string, which obviously is not a valid int format string.
// This issue should be resolved by not using the NamedField formatter
// specialization in structured.h
//// Test that there is no issues with specializations when fmt/ostream.h is
//// included after fmt/format.h.
// namespace fmt {
// template <> struct formatter<Inner> : formatter<int> {
// template <typename FormatContext>
// typename FormatContext::iterator format(const Inner&, FormatContext& ctx) {
// return formatter<int>::format(42, ctx);
// }
//};
//} // namespace fmt
#include <tuple>
#include "fmt/structured.h"
#include "gmock.h"
#include "gtest-extra.h"
using fmt::format;
using fmt::format_error;
// provide explicit reflection for custom types
template <> struct fmt::reflection<Outer> {
static constexpr bool available = true;
static auto name() { return "Outer"; }
static auto fields(const Outer& outer) {
return std::make_tuple(NamedField<std::string>{"a", outer.a},
NamedField<std::string>{"b", outer.b},
NamedField<Inner>{"inner", outer.inner});
}
static auto values(const Outer& outer) {
return std::make_tuple(outer.a, outer.b, outer.inner);
}
};
template <> struct fmt::reflection<Inner> {
static constexpr bool available = true;
static auto name() { return "Inner"; }
static auto fields(const Inner& inner) {
return std::make_tuple(NamedField<int>{"x", inner.x},
NamedField<double>{"y", inner.y},
NamedField<std::string>{"z", inner.z});
}
static auto values(const Inner& inner) {
return std::make_tuple(inner.x, inner.y, inner.z);
}
};
using namespace std::string_literals;
TEST(StructuredTest, FallbackPickup) {
Inner inner{1, 3.1, "hello"};
Outer outer{"a", "b", inner};
EXPECT_EQ("Outer{.a=a, .b=b, .inner=Inner{.x=1, .y=3.1, .z=hello}}",
fmt::format("{:e}", outer));
}