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 4 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
207 changes: 207 additions & 0 deletions iguana/enum_reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#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)};
}

// get the size of integer_sequence
template <typename T, T... I>
constexpr std::size_t integer_sequence_size(std::integer_sequence<T, I...>) {
return sizeof...(I);
}

// 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::int64_t... I1, std::int64_t... I2>
constexpr auto
concatenate_sequences(std::integer_sequence<std::int64_t, I1...>,
std::integer_sequence<std::int64_t, I2...>) {
return std::integer_sequence<std::int64_t, I1..., I2...>{};
}

// convert the array to integer sequence, number in [0, Filter) will be ignore
template <std::size_t Filter, std::int64_t N, const std::array<int, N> &arr,
std::int64_t I = N - 1>
constexpr auto array_to_seq() {
if constexpr (I > 0) {
if constexpr (arr[I] < Filter && arr[I] >= 0) {
return array_to_seq<Filter, N, arr, I - 1>();
} else {
return concatenate_sequences(
std::integer_sequence<std::int64_t, arr[I]>{},
array_to_seq<Filter, N, arr, I - 1>());
}
} else {
if constexpr (arr[I] < Filter && arr[I] >= 0) {
return std::integer_sequence<std::int64_t>();
} else {
return std::integer_sequence<std::int64_t, arr[I]>();
}
}
}

// 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 = {};
};

// by default, numbers in the range [0, Filter) will be enumerated.
template <bool str_to_enum, typename E, size_t Filter = 50>
constexpr inline auto get_enum_map() {
constexpr auto default_seq =
std::make_integer_sequence<std::int64_t, Filter>();
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<Filter, arr_size, arr>();
constexpr auto all_seq = concatenate_sequences(arr_seq, default_seq);
constexpr auto seq_size = integer_sequence_size(all_seq);
constexpr auto t = get_enum_arr<E>(all_seq);
if constexpr (str_to_enum) {
return get_str_to_enum_map<E, seq_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, seq_size>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
}
} else {
constexpr auto t = get_enum_arr<E>(default_seq);
bbbgan marked this conversation as resolved.
Show resolved Hide resolved
if constexpr (str_to_enum) {
return get_str_to_enum_map<E, Filter>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
} else {
return get_enum_to_str_map<E, Filter>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
}
}
}
#if defined(__clang__) && (__clang_major__ >= 17)
#pragma clang diagnostic pop
#endif

} // namespace iguana
25 changes: 14 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,14 @@ 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>>();
std::string_view enum_names;
parse_item(enum_names, it, end);
value = str_to_enum.find(enum_names)->second;
}

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 +636,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
4 changes: 3 additions & 1 deletion iguana/json_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ 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>>();
auto str = enum_to_str.find(val)->second;
render_json_value(ss, std::string_view(str.data(), str.size()));
}

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

This file was deleted.

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

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()));
}
}

// doctest comments
// 'function' : must be 'attribute' - see issue #182
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)
Expand Down
2 changes: 1 addition & 1 deletion test/test_util.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <iguana/util.hpp>
#include <iguana/enum_reflection.hpp>
#include <iostream>

#define DOCTEST_CONFIG_IMPLEMENT
Expand Down
Loading
Loading