Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect enum type #204

Merged
merged 5 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions iguana/enum_reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#pragma once
#include <array>
#include <string_view>

#include "frozen/string.h"
#include "frozen/unordered_map.h"

namespace iguana {
template <typename T> constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}

template <auto T> constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}

template <typename T> inline constexpr std::string_view type_string() {
constexpr std::string_view sample = get_raw_name<int>();
constexpr size_t pos = sample.find("int");
constexpr std::string_view str = get_raw_name<T>();
constexpr auto next1 = str.rfind(sample[pos + 3]);
#if defined(_MSC_VER)
constexpr auto s1 = str.substr(pos + 6, next1 - pos - 6);
#else
constexpr auto s1 = str.substr(pos, next1 - pos);
#endif
return s1;
}

template <auto T> inline constexpr std::string_view enum_string() {
constexpr std::string_view sample = get_raw_name<int>();
constexpr size_t pos = sample.find("int");
constexpr std::string_view str = get_raw_name<T>();
constexpr auto next1 = str.rfind(sample[pos + 3]);
#if defined(__clang__) || defined(_MSC_VER)
constexpr auto s1 = str.substr(pos, next1 - pos);
#else
constexpr auto s1 = str.substr(pos + 5, next1 - pos - 5);
#endif
return s1;
}

#if defined(__clang__) && (__clang_major__ >= 17)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
#endif

template <typename E, E V> constexpr std::string_view get_raw_name_with_v() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}

// True means the V is a valid enum value, and the second of the result is the
// name
template <typename E, E V>
constexpr std::pair<bool, std::string_view> try_get_enum_name() {
constexpr std::string_view sample_raw_name = get_raw_name_with_v<int, 5>();
constexpr size_t pos = sample_raw_name.find("5");
constexpr std::string_view enum_raw_name = get_raw_name_with_v<E, V>();
constexpr auto enum_end = enum_raw_name.rfind(&sample_raw_name[pos + 1]);
#ifdef _MSC_VER
constexpr auto enum_begin = enum_raw_name.rfind(',', enum_end) + 1;
#else
constexpr auto enum_begin = enum_raw_name.rfind(' ', enum_end) + 1;
#endif
constexpr std::string_view res =
enum_raw_name.substr(enum_begin, enum_end - enum_begin);

constexpr size_t pos_brackets = res.find(')');

size_t pos_colon = res.find("::");
return {pos_brackets == std::string_view::npos,
res.substr(pos_colon == std::string_view::npos ? 0 : pos_colon + 2)};
}

// Enumerate the numbers in a integer sequence to see if they are legal enum
// value
template <typename E, std::int64_t... Is>
constexpr inline auto
get_enum_arr(const std::integer_sequence<std::int64_t, Is...> &) {
constexpr std::size_t N = sizeof...(Is);
std::array<std::string_view, N> enum_names = {};
std::array<E, N> enum_values = {};
std::size_t num = 0;
(([&]() {
constexpr auto res = try_get_enum_name<E, static_cast<E>(Is)>();
if constexpr (res.first) {
// the Is is a valid enum value
enum_names[num] = res.second;
enum_values[num] = static_cast<E>(Is);
++num;
}
})(),
...);
return std::make_tuple(num, enum_values, enum_names);
}

template <std::size_t N, const std::array<int, N> &arr, size_t... Is>
constexpr auto array_to_seq(const std::index_sequence<Is...> &) {
return std::integer_sequence<std::int64_t, arr[Is]...>();
}

// convert array to map
template <typename E, size_t N, size_t... Is>
constexpr inline auto
get_enum_to_str_map(const std::array<std::string_view, N> &enum_names,
const std::array<E, N> &enum_values,
const std::index_sequence<Is...> &) {
return frozen::unordered_map<E, frozen::string, sizeof...(Is)>{
{enum_values[Is], enum_names[Is]}...};
}

template <typename E, size_t N, size_t... Is>
constexpr inline auto
get_str_to_enum_map(const std::array<std::string_view, N> &enum_names,
const std::array<E, N> &enum_values,
const std::index_sequence<Is...> &) {
return frozen::unordered_map<frozen::string, E, sizeof...(Is)>{
{enum_names[Is], enum_values[Is]}...};
}

// the default generic enum_value
// if the user has not defined a specialization template, this will be called
template <typename T> struct enum_value {
constexpr static std::array<int, 0> value = {};
};

template <bool str_to_enum, typename E> constexpr inline auto get_enum_map() {
constexpr auto &arr = enum_value<E>::value;
constexpr auto arr_size = arr.size();
if constexpr (arr_size > 0) {
// the user has defined a specialization template
constexpr auto arr_seq =
array_to_seq<arr_size, arr>(std::make_index_sequence<arr_size>());
constexpr auto t = get_enum_arr<E>(arr_seq);
if constexpr (str_to_enum) {
return get_str_to_enum_map<E, arr_size>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
} else {
return get_enum_to_str_map<E, arr_size>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
}
} else {
return false;
}
}

#if defined(__clang__) && (__clang_major__ >= 17)
#pragma clang diagnostic pop
#endif

} // namespace iguana
37 changes: 26 additions & 11 deletions iguana/json_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ IGUANA_INLINE void parse_escape(U &value, It &&it, It &&end) {
if (*it == 'u') {
++it;
if (std::distance(it, end) <= 4)
throw std::runtime_error(R"(Expected 4 hexadecimal digits)");
IGUANA_UNLIKELY {
throw std::runtime_error(R"(Expected 4 hexadecimal digits)");
}
auto code_point = parse_unicode_hex4(it);
encode_utf8(value, code_point);
} else if (*it == 'n') {
Expand Down Expand Up @@ -87,12 +89,6 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
std::string_view(&*start, static_cast<size_t>(std::distance(start, it)));
}

template <typename U, typename It, std::enable_if_t<enum_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
using T = std::underlying_type_t<std::decay_t<U>>;
parse_item(reinterpret_cast<T &>(value), it, end);
}

template <bool skip = false, typename U, typename It,
std::enable_if_t<char_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
Expand Down Expand Up @@ -183,7 +179,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
IGUANA_UNLIKELY case '\\' : ++it;
parse_escape(value, it, end);
break;
IGUANA_UNLIKELY case ']' : return;
// IGUANA_UNLIKELY case ']' : return;
IGUANA_UNLIKELY case '"' : ++it;
return;
IGUANA_LIKELY default : value.push_back(*it);
Expand Down Expand Up @@ -215,6 +211,26 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
throw std::runtime_error("Expected \"");
}

template <typename U, typename It, std::enable_if_t<enum_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
static constexpr auto str_to_enum = get_enum_map<true, std::decay_t<U>>();
if constexpr (bool_v<decltype(str_to_enum)>) {
// not defined a specialization template
using T = std::underlying_type_t<std::decay_t<U>>;
parse_item(reinterpret_cast<T &>(value), it, end);
} else {
std::string_view enum_names;
parse_item(enum_names, it, end);
auto it = str_to_enum.find(enum_names);
if (it != str_to_enum.end())
IGUANA_LIKELY { value = it->second; }
else {
throw std::runtime_error(std::string(enum_names) +
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个分支覆盖到了吗,确保每个分支都覆盖到。

" missing corresponding value in enum_value");
}
}
}

template <typename U, typename It, std::enable_if_t<fixed_array_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
using T = std::remove_reference_t<U>;
Expand Down Expand Up @@ -632,9 +648,8 @@ inline void parse_object(jobject &result, It &&it, It &&end) {
skip_ws(it, end);

while (true) {
if (it == end) {
break;
}
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Expected }"); }
std::string key;
detail::parse_item(key, it, end);

Expand Down
1 change: 1 addition & 0 deletions iguana/json_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "define.h"
#include "detail/charconv.h"
#include "enum_reflection.hpp"
#include "reflection.hpp"
#include "value.hpp"
#include <filesystem>
Expand Down
17 changes: 16 additions & 1 deletion iguana/json_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,22 @@ IGUANA_INLINE void render_json_value(Stream &ss, T &&t) {

template <typename Stream, typename T, std::enable_if_t<enum_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T val) {
render_json_value(ss, static_cast<std::underlying_type_t<T>>(val));
static constexpr auto enum_to_str = get_enum_map<false, std::decay_t<T>>();
if constexpr (bool_v<decltype(enum_to_str)>) {
render_json_value(ss, static_cast<std::underlying_type_t<T>>(val));
} else {
auto it = enum_to_str.find(val);
if (it != enum_to_str.end())
IGUANA_LIKELY {
auto str = it->second;
render_json_value(ss, std::string_view(str.data(), str.size()));
}
else {
throw std::runtime_error(
std::to_string(static_cast<std::underlying_type_t<T>>(val)) +
" is a missing value in enum_value");
}
}
}

template <typename Stream, typename T>
Expand Down
46 changes: 0 additions & 46 deletions iguana/util.hpp

This file was deleted.

74 changes: 74 additions & 0 deletions test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,80 @@ TEST_CASE("check some types") {
value_type{std::in_place_index_t<1>{}, &point_t::y});
}

enum class Status { STOP = 10, START };
namespace iguana {
template <> struct enum_value<Status> {
constexpr static std::array<int, 1> value = {10};
};
} // namespace iguana
TEST_CASE("test exception") {
{
std::string str = R"({"\u8001": "name"})";
std::unordered_map<std::string_view, std::string> mp;
iguana::from_json(mp, str);
CHECK(mp["老"] == "name");
}
{
std::string str = R"({"a": "\)";
std::unordered_map<std::string, std::string> mp;
CHECK_THROWS(iguana::from_json(mp, str));
}
{
std::string str = R"({"a": "\u8")";
std::unordered_map<std::string, std::string> mp;
CHECK_THROWS(iguana::from_json(mp, str));
}
{
std::string str = R"([10, d5])";
std::deque<char> char_q(str.begin(), str.end());
std::vector<int> arr;
CHECK_THROWS(iguana::from_json(arr, char_q));
}
{
std::string str = R"({"a": "\)";
std::unordered_map<std::string, char> mp;
CHECK_THROWS(iguana::from_json(mp, str));
}
{
std::string str = R"({"a: "\")";
std::unordered_map<std::string, std::string_view> mp;
CHECK_THROWS(iguana::from_json(mp, str));
}
{
std::string str = R"(bb)";
iguana::jvalue val;
CHECK_THROWS(iguana::parse(val, str.begin(), str.end()));
}
{
// THREW exception: duplicated key a
std::string str = R"({"a": "b", "a": "c"})";
iguana::jvalue val;
CHECK_THROWS(iguana::parse(val, str.begin(), str.end()));
}
{
std::string str = R"({"a": "c",)";
iguana::jvalue val;
CHECK_THROWS(iguana::parse(val, str.begin(), str.end()));
}
{
std::string str = R"(
{
"a": "START",
"b": "STOP",
}
)";
std::unordered_map<std::string, Status> mp;
CHECK_THROWS(iguana::from_json(mp, str));
}
{
std::unordered_map<std::string, Status> mp;
mp["a"] = Status::START;
mp["b"] = Status::STOP;
std::string ss;
CHECK_THROWS(iguana::to_json(mp, ss));
}
}

// doctest comments
// 'function' : must be 'attribute' - see issue #182
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)
Expand Down
Loading
Loading