318 lines
11 KiB
C++
318 lines
11 KiB
C++
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2019, Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "fwd.hpp"
|
|
|
|
#include "../view.hpp"
|
|
#include "../introspection.hpp"
|
|
#include "../log.hpp"
|
|
|
|
#include <map>
|
|
|
|
namespace cruft::parse::enumeration {
|
|
namespace detail {
|
|
/// Map a type to the underlying enum type if it is an enum, else
|
|
/// provided the identity mapping. Useful so simplify template logic
|
|
/// when dealing with types that aren't quite enums proper.
|
|
template <typename EnumT, typename = void>
|
|
struct underlying_else_identity { using type = EnumT; };
|
|
|
|
|
|
// Specialise for the enum case.
|
|
template <typename EnumT>
|
|
struct underlying_else_identity<
|
|
EnumT,
|
|
std::enable_if_t<std::is_enum_v<EnumT>>
|
|
> { using type = std::underlying_type_t<EnumT>; };
|
|
|
|
|
|
template <typename EnumT>
|
|
struct underlying_else_identity<
|
|
EnumT,
|
|
std::void_t<typename EnumT::underlying_type>
|
|
> { using type = typename underlying_else_identity<typename EnumT::underlying_type>::type; };
|
|
|
|
|
|
template <typename EnumT>
|
|
using underlying_else_identity_t = typename underlying_else_identity<EnumT>::type;
|
|
|
|
|
|
/// Abstract base class for lookups from string names to typed
|
|
/// integers.
|
|
///
|
|
/// An exception should be thrown if the type is incorrect. This
|
|
/// shouldn't be the case because the caller is expected to know the
|
|
/// type beforehand.
|
|
///
|
|
/// Do not perform type casting - lossless or otherwise.
|
|
struct lookup_base {
|
|
virtual ~lookup_base () = default;
|
|
|
|
virtual i08 as_i08 (cruft::view<char const*> &) const& = 0;
|
|
virtual i16 as_i16 (cruft::view<char const*> &) const& = 0;
|
|
virtual i32 as_i32 (cruft::view<char const*> &) const& = 0;
|
|
virtual i64 as_i64 (cruft::view<char const*> &) const& = 0;
|
|
|
|
virtual u08 as_u08 (cruft::view<char const*> &) const& = 0;
|
|
virtual u16 as_u16 (cruft::view<char const*> &) const& = 0;
|
|
virtual u32 as_u32 (cruft::view<char const*> &) const& = 0;
|
|
virtual u64 as_u64 (cruft::view<char const*> &) const& = 0;
|
|
};
|
|
|
|
|
|
template <typename EnumT>
|
|
struct lookup_callback : public lookup_base {
|
|
using function_t = std::function<EnumT(cruft::view<char const*>&)>;
|
|
|
|
lookup_callback (
|
|
function_t &&_callback
|
|
) : m_callback (std::move (_callback))
|
|
{ ; }
|
|
|
|
i08 as_i08 (cruft::view<char const*> &str) const& override { return get<i08> (str); }
|
|
i16 as_i16 (cruft::view<char const*> &str) const& override { return get<i16> (str); }
|
|
i32 as_i32 (cruft::view<char const*> &str) const& override { return get<i32> (str); }
|
|
i64 as_i64 (cruft::view<char const*> &str) const& override { return get<i64> (str); }
|
|
|
|
u08 as_u08 (cruft::view<char const*> &str) const& override { return get<u08> (str); }
|
|
u16 as_u16 (cruft::view<char const*> &str) const& override { return get<u16> (str); }
|
|
u32 as_u32 (cruft::view<char const*> &str) const& override { return get<u32> (str); }
|
|
u64 as_u64 (cruft::view<char const*> &str) const& override { return get<u64> (str); }
|
|
|
|
private:
|
|
template <typename ValueT>
|
|
ValueT
|
|
get (cruft::view<char const*> &str) const&
|
|
{
|
|
if constexpr (std::is_same_v<ValueT, underlying_else_identity_t<EnumT>>) {
|
|
// Double check the callee changes the remaining string size.
|
|
auto const len = str.size ();
|
|
auto const res = m_callback (str);
|
|
if (len == str.size ())
|
|
throw std::invalid_argument ("Unknown enum value");
|
|
return static_cast<ValueT> (res);
|
|
} else {
|
|
throw std::runtime_error ("Invalid underlying type");
|
|
}
|
|
}
|
|
|
|
|
|
function_t m_callback;
|
|
};
|
|
|
|
|
|
/// A simple lookup from a supplied mapping of strings-to-underlying,
|
|
/// specialised on the EnumT that the lookup is targetting.
|
|
template <typename EnumT>
|
|
struct lookup_concrete : public lookup_base {
|
|
using underlying_type = underlying_else_identity_t<EnumT>;
|
|
using cache_type = std::map<std::string_view, EnumT>;
|
|
|
|
lookup_concrete (cache_type _cache)
|
|
: m_cache (std::move (_cache))
|
|
{ ; }
|
|
|
|
i08 as_i08 (cruft::view<char const*> &str) const& override { return get<i08> (str); }
|
|
i16 as_i16 (cruft::view<char const*> &str) const& override { return get<i16> (str); }
|
|
i32 as_i32 (cruft::view<char const*> &str) const& override { return get<i32> (str); }
|
|
i64 as_i64 (cruft::view<char const*> &str) const& override { return get<i64> (str); }
|
|
|
|
u08 as_u08 (cruft::view<char const*> &str) const& override { return get<u08> (str); }
|
|
u16 as_u16 (cruft::view<char const*> &str) const& override { return get<u16> (str); }
|
|
u32 as_u32 (cruft::view<char const*> &str) const& override { return get<u32> (str); }
|
|
u64 as_u64 (cruft::view<char const*> &str) const& override { return get<u64> (str); }
|
|
|
|
private:
|
|
/// A common lookup helper for strings-to-some_value_type.
|
|
/// Provided so that we don't need to replicate the lookup and
|
|
/// error logic for each integer case.
|
|
template <typename ValueT>
|
|
ValueT
|
|
get (cruft::view<char const*> &str) const&
|
|
{
|
|
static_assert (std::is_integral_v<ValueT>);
|
|
|
|
if constexpr (std::is_same_v<ValueT, underlying_type>) {
|
|
for (auto const [key,val]: m_cache) {
|
|
if (equal (key, str)) {
|
|
str = str.consume (key.size ());
|
|
// We must cast the value here so that enum
|
|
// classes are supported.
|
|
return static_cast<ValueT> (val);
|
|
}
|
|
}
|
|
|
|
throw std::invalid_argument ("Unknown enum value");
|
|
} else {
|
|
throw std::runtime_error ("Invalid underlying type");
|
|
}
|
|
}
|
|
|
|
cache_type m_cache;
|
|
};
|
|
|
|
|
|
/// Resolves a global map from typeidx to lookup providers for the
|
|
/// enumeration type.
|
|
std::map<
|
|
int,
|
|
std::unique_ptr<lookup_base>
|
|
>&
|
|
cache (void);
|
|
|
|
|
|
template <typename EnumT>
|
|
cookie
|
|
setup [[nodiscard]] (std::unique_ptr<lookup_base> &&lookup)
|
|
{
|
|
auto &cache = detail::cache ();
|
|
auto const index = cruft::typeidx<EnumT> ();
|
|
|
|
auto [pos, success] = cache.insert ({
|
|
index,
|
|
std::move (lookup)
|
|
});
|
|
|
|
if (!success)
|
|
LOG_WARN ("duplicate parse setup for %! was ignored", cruft::type_name<EnumT> ());
|
|
|
|
return cookie {};
|
|
}
|
|
};
|
|
|
|
|
|
/// Establish a mapping from strings-to-enums and return a cookie that
|
|
/// will deregister the mapping at destruction time.
|
|
///
|
|
/// Results when calling multiple times for the same type are undefined
|
|
/// though some form of runtime warning will likely be emitted, and it's
|
|
/// unlikely to crash your application).
|
|
template <typename EnumT>
|
|
cookie
|
|
setup [[nodiscard]] (std::map<std::string_view, EnumT> mapping)
|
|
{
|
|
return detail::setup<EnumT> (
|
|
std::make_unique<
|
|
detail::lookup_concrete<EnumT>
|
|
> (std::move (mapping))
|
|
);
|
|
};
|
|
|
|
|
|
template <typename EnumT>
|
|
cookie
|
|
setup [[nodiscard]] (std::function<EnumT(cruft::view<char const*>&)> &&func)
|
|
{
|
|
return detail::setup<EnumT> (
|
|
std::make_unique<
|
|
detail::lookup_callback<EnumT>
|
|
> (std::move (func))
|
|
);
|
|
}
|
|
|
|
|
|
/// Returns true if a typeidx has been registered for enumeration
|
|
/// reflection.
|
|
inline
|
|
bool is_registered (int idx)
|
|
{
|
|
auto const &dat = detail::cache ();
|
|
auto const pos = dat.find (idx);
|
|
return pos != dat.cend ();
|
|
}
|
|
|
|
|
|
/// Returns true if a typename has been registered for enumation
|
|
/// reflection.
|
|
template <typename ValueT>
|
|
bool is_registered (void)
|
|
{
|
|
return is_registered (cruft::typeidx<ValueT> ());
|
|
}
|
|
|
|
|
|
/// Lookup an enumeration value given a string representation.
|
|
///
|
|
/// The string view is taken by reference and will be modified so that it
|
|
/// points to the remainder of the string after the parsed value.
|
|
template <typename EnumT>
|
|
EnumT
|
|
value (int const idx, cruft::view<char const*> &str)
|
|
{
|
|
auto const ® = detail::cache ();
|
|
auto const pos = reg.find (idx);
|
|
|
|
if (pos == reg.cend ())
|
|
throw std::invalid_argument ("Unknown enumeration");
|
|
|
|
auto const &obj = *pos->second;
|
|
|
|
static_assert (
|
|
sizeof (EnumT) == 1 ||
|
|
sizeof (EnumT) == 2 ||
|
|
sizeof (EnumT) == 4 ||
|
|
sizeof (EnumT) == 8
|
|
);
|
|
|
|
using underlying_type = detail::underlying_else_identity_t<EnumT>;
|
|
static_assert (std::is_integral_v<underlying_type>);
|
|
|
|
if constexpr (std::is_signed_v<underlying_type>) {
|
|
if constexpr (sizeof (EnumT) == 1) return static_cast<EnumT> (obj.as_i08 (str));
|
|
if constexpr (sizeof (EnumT) == 2) return static_cast<EnumT> (obj.as_i16 (str));
|
|
if constexpr (sizeof (EnumT) == 4) return static_cast<EnumT> (obj.as_i32 (str));
|
|
if constexpr (sizeof (EnumT) == 8) return static_cast<EnumT> (obj.as_i64 (str));
|
|
|
|
unreachable ();
|
|
} else {
|
|
if constexpr (sizeof (EnumT) == 1) return static_cast<EnumT> (obj.as_u08 (str));
|
|
if constexpr (sizeof (EnumT) == 2) return static_cast<EnumT> (obj.as_u16 (str));
|
|
if constexpr (sizeof (EnumT) == 4) return static_cast<EnumT> (obj.as_u32 (str));
|
|
if constexpr (sizeof (EnumT) == 8) return static_cast<EnumT> (obj.as_u64 (str));
|
|
|
|
unreachable ();
|
|
}
|
|
}
|
|
|
|
|
|
template <typename EnumT>
|
|
EnumT
|
|
value (cruft::view<char const*> &str)
|
|
{
|
|
return value<EnumT> (typeidx<EnumT> (), str);
|
|
}
|
|
|
|
|
|
/// Lookup an enumeration value from the string representation given a
|
|
/// typeidx.
|
|
///
|
|
/// If the entire string isn't consumed an exception will be thrown.
|
|
template <typename EnumT>
|
|
EnumT
|
|
from_string (int const idx, cruft::view<char const*> src)
|
|
{
|
|
auto const res = value<EnumT> (idx, src);
|
|
if (!src.empty ())
|
|
throw std::runtime_error ("Invalid conversion");
|
|
return res;
|
|
}
|
|
|
|
|
|
/// Lookup an enumeration value from the string representation
|
|
///
|
|
/// If the entire string isn't consumed an exception will be thrown.
|
|
template <typename EnumT>
|
|
EnumT
|
|
from_string (cruft::view<char const*> src)
|
|
{
|
|
return from_string<EnumT> (typeidx<EnumT> (), src);
|
|
}
|
|
}
|