/* * 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 */ #pragma once #include "fwd.hpp" #include "../platform.hpp" #include "../debug/panic.hpp" #include "../introspection/name.hpp" #include "../log.hpp" #include "../typeidx.hpp" #include "../view.hpp" #include 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 struct underlying_else_identity { using type = EnumT; }; // Specialise for the enum case. template struct underlying_else_identity< EnumT, std::enable_if_t> > { using type = std::underlying_type_t; }; template struct underlying_else_identity< EnumT, std::void_t > { using type = typename underlying_else_identity::type; }; template using underlying_else_identity_t = typename underlying_else_identity::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 &) const& = 0; virtual i16 as_i16 (cruft::view &) const& = 0; virtual i32 as_i32 (cruft::view &) const& = 0; virtual i64 as_i64 (cruft::view &) const& = 0; virtual u08 as_u08 (cruft::view &) const& = 0; virtual u16 as_u16 (cruft::view &) const& = 0; virtual u32 as_u32 (cruft::view &) const& = 0; virtual u64 as_u64 (cruft::view &) const& = 0; }; template struct lookup_callback : public lookup_base { using function_t = std::function&)>; lookup_callback ( function_t &&_callback ) : m_callback (std::move (_callback)) { ; } i08 as_i08 (cruft::view &str) const& override { return get (str); } i16 as_i16 (cruft::view &str) const& override { return get (str); } i32 as_i32 (cruft::view &str) const& override { return get (str); } i64 as_i64 (cruft::view &str) const& override { return get (str); } u08 as_u08 (cruft::view &str) const& override { return get (str); } u16 as_u16 (cruft::view &str) const& override { return get (str); } u32 as_u32 (cruft::view &str) const& override { return get (str); } u64 as_u64 (cruft::view &str) const& override { return get (str); } private: template ValueT get (cruft::view &str) const& { if constexpr (std::is_same_v>) { // 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 (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 struct lookup_concrete : public lookup_base { using underlying_type = underlying_else_identity_t; using cache_type = std::map; lookup_concrete (cache_type _cache) : m_cache (std::move (_cache)) { ; } i08 as_i08 (cruft::view &str) const& override { return get (str); } i16 as_i16 (cruft::view &str) const& override { return get (str); } i32 as_i32 (cruft::view &str) const& override { return get (str); } i64 as_i64 (cruft::view &str) const& override { return get (str); } u08 as_u08 (cruft::view &str) const& override { return get (str); } u16 as_u16 (cruft::view &str) const& override { return get (str); } u32 as_u32 (cruft::view &str) const& override { return get (str); } u64 as_u64 (cruft::view &str) const& override { return get (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 ValueT get (cruft::view &str) const& { static_assert (std::is_integral_v); if constexpr (std::is_same_v) { 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 (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 >& cache (void); template cookie setup [[nodiscard]] (std::unique_ptr &&lookup) { auto &cache = detail::cache (); auto const index = cruft::typeidx (); auto [pos, success] = cache.insert ({ index, std::move (lookup) }); if (!success) // clang#XXX // fmtlib#1824 // generates "unused type alias 'char_type'" under clang #if defined(COMPILER_CLANG) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-local-typedef" #endif LOG_WARN ( "duplicate parse setup for {} was ignored", cruft::introspection::name::bare () ); #if defined(COMPILER_CLANG) #pragma GCC diagnostic push -Wno-unused-local-typedef #endif 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 cookie setup [[nodiscard]] (std::map mapping) { return detail::setup ( std::make_unique< detail::lookup_concrete > (std::move (mapping)) ); }; template cookie setup [[nodiscard]] (std::function&)> &&func) { return detail::setup ( std::make_unique< detail::lookup_callback > (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 bool is_registered (void) { return is_registered (cruft::typeidx ()); } /// 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 EnumT value (int const idx, cruft::view &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; static_assert (std::is_integral_v); if constexpr (std::is_signed_v) { if constexpr (sizeof (EnumT) == 1) return static_cast (obj.as_i08 (str)); if constexpr (sizeof (EnumT) == 2) return static_cast (obj.as_i16 (str)); if constexpr (sizeof (EnumT) == 4) return static_cast (obj.as_i32 (str)); if constexpr (sizeof (EnumT) == 8) return static_cast (obj.as_i64 (str)); unreachable (); } else { if constexpr (sizeof (EnumT) == 1) return static_cast (obj.as_u08 (str)); if constexpr (sizeof (EnumT) == 2) return static_cast (obj.as_u16 (str)); if constexpr (sizeof (EnumT) == 4) return static_cast (obj.as_u32 (str)); if constexpr (sizeof (EnumT) == 8) return static_cast (obj.as_u64 (str)); unreachable (); } } template EnumT value (cruft::view &str) { return value (typeidx (), 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 EnumT from_string (int const idx, cruft::view src) { auto const res = value (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 EnumT from_string (cruft::view src) { return from_string (typeidx (), src); } }