first very hacky poc
This commit is contained in:
parent
37a5ecdd2c
commit
5df04d4634
146
include/fmt/structured.h
Normal file
146
include/fmt/structured.h
Normal 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
|
||||||
@ -103,6 +103,9 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
|||||||
endif ()
|
endif ()
|
||||||
add_fmt_test(locale-test)
|
add_fmt_test(locale-test)
|
||||||
add_fmt_test(ostream-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(compile-test)
|
||||||
add_fmt_test(printf-test)
|
add_fmt_test(printf-test)
|
||||||
add_fmt_test(custom-formatter-test)
|
add_fmt_test(custom-formatter-test)
|
||||||
|
|||||||
83
test/reflect-struct-test.cc
Normal file
83
test/reflect-struct-test.cc
Normal 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));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user