Add easy to use macro to generate enum helper functions for parsing, stringifying and iteration.
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2020-02-12 19:48:56 +01:00
parent a8944e2026
commit 2732a904c4
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
8 changed files with 170 additions and 48 deletions

52
src/Core/Enum.hpp Normal file
View File

@ -0,0 +1,52 @@
#include <algorithm>
#include <string>
#include "Exceptions/NotReachableException.hpp"
#include "MacroUtils.hpp"
#define ENUM_CASE(x, name) \
case name::x: \
return #x;
#
#define ENUM_PARSE_CASE(x, name) \
case ConstHash(#x): \
return name::x;
#
#define ENUM_PARSE_CASE_INSENSITIVE(x, name) \
case ConstHashCI(#x): \
return name::x;
#
#define ARRAY_NAME(x, name) name::x,
#define ENUM(name, type, values...) \
enum class name : type { values }; \
class name##Helper { \
inline static uint32_t constexpr ConstHash(char const* input) { \
return *input ? static_cast<uint32_t>(*input) + 33 * ConstHash(input + 1) : 5381; \
} \
inline static constexpr char charToLower(const char c) { \
return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; \
} \
inline static uint32_t constexpr ConstHashCI(char const* input) { \
return charToLower(*input) ? static_cast<uint32_t>(charToLower(*input)) + 33 * ConstHashCI(input + 1) \
: 5381; \
} \
\
public: \
static std::string ToString(name value) { \
switch (value) { FOR_EACH(ENUM_CASE, name, values) } \
throw NotReachableException(); \
} \
static name Parse(const std::string& input, bool caseInsensitive = false) { \
if (caseInsensitive) \
return ParseCaseInsensitive(input); \
switch (ConstHash(input.c_str())) { FOR_EACH(ENUM_PARSE_CASE, name, values) } \
throw CreatureException("String '" + input + "' could not be parsed as " #name); \
} \
static std::vector<name> GetValues() { return {FOR_EACH(ARRAY_NAME, name, values)}; } \
\
private: \
static name ParseCaseInsensitive(const std::string& input) { \
switch (ConstHashCI(input.c_str())) { FOR_EACH(ENUM_PARSE_CASE_INSENSITIVE, name, values) } \
throw CreatureException("String '" + input + "' could not be parsed as " #name); \
} \
};

43
src/Core/MacroUtils.hpp Normal file
View File

@ -0,0 +1,43 @@
#define FE_0(FUNC, arg)
#define FE_1(FUNC, arg, X) FUNC(X, arg)
#define FE_2(FUNC, arg, X, ...) FUNC(X, arg) FE_1(FUNC, arg, __VA_ARGS__)
#define FE_3(FUNC, arg, X, ...) FUNC(X, arg) FE_2(FUNC, arg, __VA_ARGS__)
#define FE_4(FUNC, arg, X, ...) FUNC(X, arg) FE_3(FUNC, arg, __VA_ARGS__)
#define FE_5(FUNC, arg, X, ...) FUNC(X, arg) FE_4(FUNC, arg, __VA_ARGS__)
#define FE_6(FUNC, arg, X, ...) FUNC(X, arg) FE_5(FUNC, arg, __VA_ARGS__)
#define FE_7(FUNC, arg, X, ...) FUNC(X, arg) FE_6(FUNC, arg, __VA_ARGS__)
#define FE_8(FUNC, arg, X, ...) FUNC(X, arg) FE_7(FUNC, arg, __VA_ARGS__)
#define FE_9(FUNC, arg, X, ...) FUNC(X, arg) FE_8(FUNC, arg, __VA_ARGS__)
#define FE_10(FUNC, arg, X, ...) FUNC(X, arg) FE_9(FUNC, arg, __VA_ARGS__)
#define FE_11(FUNC, arg, X, ...) FUNC(X, arg) FE_10(FUNC, arg, __VA_ARGS__)
#define FE_12(FUNC, arg, X, ...) FUNC(X, arg) FE_11(FUNC, arg, __VA_ARGS__)
#define FE_13(FUNC, arg, X, ...) FUNC(X, arg) FE_12(FUNC, arg, __VA_ARGS__)
#define FE_14(FUNC, arg, X, ...) FUNC(X, arg) FE_13(FUNC, arg, __VA_ARGS__)
#define FE_15(FUNC, arg, X, ...) FUNC(X, arg) FE_14(FUNC, arg, __VA_ARGS__)
#define FE_16(FUNC, arg, X, ...) FUNC(X, arg) FE_15(FUNC, arg, __VA_ARGS__)
#define FE_17(FUNC, arg, X, ...) FUNC(X, arg) FE_16(FUNC, arg, __VA_ARGS__)
#define FE_18(FUNC, arg, X, ...) FUNC(X, arg) FE_17(FUNC, arg, __VA_ARGS__)
#define FE_19(FUNC, arg, X, ...) FUNC(X, arg) FE_18(FUNC, arg, __VA_ARGS__)
#define FE_20(FUNC, arg, X, ...) FUNC(X, arg) FE_19(FUNC, arg, __VA_ARGS__)
#define FE_21(FUNC, arg, X, ...) FUNC(X, arg) FE_20(FUNC, arg, __VA_ARGS__)
#define FE_22(FUNC, arg, X, ...) FUNC(X, arg) FE_21(FUNC, arg, __VA_ARGS__)
#define FE_23(FUNC, arg, X, ...) FUNC(X, arg) FE_22(FUNC, arg, __VA_ARGS__)
#define FE_24(FUNC, arg, X, ...) FUNC(X, arg) FE_23(FUNC, arg, __VA_ARGS__)
#define FE_25(FUNC, arg, X, ...) FUNC(X, arg) FE_24(FUNC, arg, __VA_ARGS__)
#define FE_26(FUNC, arg, X, ...) FUNC(X, arg) FE_25(FUNC, arg, __VA_ARGS__)
#define FE_27(FUNC, arg, X, ...) FUNC(X, arg) FE_26(FUNC, arg, __VA_ARGS__)
#define FE_28(FUNC, arg, X, ...) FUNC(X, arg) FE_27(FUNC, arg, __VA_ARGS__)
#define FE_29(FUNC, arg, X, ...) FUNC(X, arg) FE_28(FUNC, arg, __VA_ARGS__)
#define FE_30(FUNC, arg, X, ...) FUNC(X, arg) FE_29(FUNC, arg, __VA_ARGS__)
#define FE_31(FUNC, arg, X, ...) FUNC(X, arg) FE_30(FUNC, arg, __VA_ARGS__)
#define FE_32(FUNC, arg, X, ...) FUNC(X, arg) FE_31(FUNC, arg, __VA_ARGS__)
#define FE_33(FUNC, arg, X, ...) THE_FOREACH_MACRO_CURRENTLY_ONLY_SUPPORTS_UP_TO_32_VALUES FE_32(FUNC, arg, __VA_ARGS__)
#define GET_MACRO(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
_22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, NAME, ...) \
NAME
#define FOR_EACH(action, arg, ...) \
GET_MACRO(_0, __VA_ARGS__, FE_33, FE_32, FE_31, FE_30, FE_29, FE_28, FE_27, FE_26, FE_25, FE_24, FE_23, FE_22, \
FE_21, FE_20, FE_19, FE_18, FE_17, FE_16, FE_15, FE_14, FE_13, FE_12, FE_11, FE_10, FE_9, FE_8, FE_7, \
FE_6, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0) \
(action, arg, __VA_ARGS__)

View File

@ -2,16 +2,10 @@
#define CREATURELIB_STATISTIC_HPP #define CREATURELIB_STATISTIC_HPP
#include <cstdint> #include <cstdint>
#include "Enum.hpp"
namespace CreatureLib::Core { namespace CreatureLib::Core {
enum Statistic : uint8_t { ENUM(Statistic, uint8_t, Health, PhysicalAttack, PhysicalDefense, MagicalAttack, MagicalDefense, Speed)
Health,
PhysicalAttack,
PhysicalDefense,
MagicalAttack,
MagicalDefense,
Speed
};
} }
#endif // CREATURELIB_STATISTIC_HPP #endif // CREATURELIB_STATISTIC_HPP

View File

@ -31,47 +31,47 @@ namespace CreatureLib::Core {
[[nodiscard]] inline T GetStat(Statistic stat) const { [[nodiscard]] inline T GetStat(Statistic stat) const {
switch (stat) { switch (stat) {
case Health: return _health; case CreatureLib::Core::Statistic::Health: return _health;
case PhysicalAttack: return _physicalAttack; case CreatureLib::Core::Statistic::PhysicalAttack: return _physicalAttack;
case PhysicalDefense: return _physicalDefense; case CreatureLib::Core::Statistic::PhysicalDefense: return _physicalDefense;
case MagicalAttack: return _magicalAttack; case CreatureLib::Core::Statistic::MagicalAttack: return _magicalAttack;
case MagicalDefense: return _magicalDefense; case CreatureLib::Core::Statistic::MagicalDefense: return _magicalDefense;
case Speed: return _speed; case CreatureLib::Core::Statistic::Speed: return _speed;
default: throw NotReachableException(); default: throw NotReachableException();
} }
} }
inline void SetStat(Statistic stat, T value) { inline void SetStat(Statistic stat, T value) {
switch (stat) { switch (stat) {
case Health: _health = value; break; case CreatureLib::Core::Statistic::Health: _health = value; break;
case PhysicalAttack: _physicalAttack = value; break; case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack = value; break;
case PhysicalDefense: _physicalDefense = value; break; case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense = value; break;
case MagicalAttack: _magicalAttack = value; break; case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack = value; break;
case MagicalDefense: _magicalDefense = value; break; case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense = value; break;
case Speed: _speed = value; break; case CreatureLib::Core::Statistic::Speed: _speed = value; break;
default: throw NotReachableException(); default: throw NotReachableException();
} }
} }
inline void IncreaseStatBy(Statistic stat, T amount) { inline void IncreaseStatBy(Statistic stat, T amount) {
switch (stat) { switch (stat) {
case Health: _health += amount; break; case CreatureLib::Core::Statistic::Health: _health += amount; break;
case PhysicalAttack: _physicalAttack += amount; break; case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack += amount; break;
case PhysicalDefense: _physicalDefense += amount; break; case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense += amount; break;
case MagicalAttack: _magicalAttack += amount; break; case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack += amount; break;
case MagicalDefense: _magicalDefense += amount; break; case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense += amount; break;
case Speed: _speed += amount; break; case CreatureLib::Core::Statistic::Speed: _speed += amount; break;
default: throw NotReachableException(); default: throw NotReachableException();
} }
} }
inline void DecreaseStatBy(Statistic stat, T amount) { inline void DecreaseStatBy(Statistic stat, T amount) {
switch (stat) { switch (stat) {
case Health: _health -= amount; break; case CreatureLib::Core::Statistic::Health: _health -= amount; break;
case PhysicalAttack: _physicalAttack -= amount; break; case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack -= amount; break;
case PhysicalDefense: _physicalDefense -= amount; break; case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense -= amount; break;
case MagicalAttack: _magicalAttack -= amount; break; case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack -= amount; break;
case MagicalDefense: _magicalDefense -= amount; break; case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense -= amount; break;
case Speed: _speed -= amount; break; case CreatureLib::Core::Statistic::Speed: _speed -= amount; break;
default: throw NotReachableException(); default: throw NotReachableException();
} }
} }

View File

@ -2,9 +2,10 @@
#define CREATURELIB_ATTACKCATEGORY_HPP #define CREATURELIB_ATTACKCATEGORY_HPP
#include <cstdint> #include <cstdint>
#include "../../Core/Enum.hpp"
namespace CreatureLib::Library { namespace CreatureLib::Library {
enum class AttackCategory : uint8_t { Physical, Magical, Status }; ENUM(AttackCategory, uint8_t, Physical, Magical, Status)
} }
#endif // CREATURELIB_ATTACKCATEGORY_HPP #endif // CREATURELIB_ATTACKCATEGORY_HPP

View File

@ -2,25 +2,16 @@
#define CREATURELIB_ATTACKTARGET_HPP #define CREATURELIB_ATTACKTARGET_HPP
#include <cstdint> #include <cstdint>
#include "../../Core/Enum.hpp"
namespace CreatureLib::Library { namespace CreatureLib::Library {
enum class AttackTarget : uint8_t { ENUM(AttackTarget, uint8_t, Adjacent, AdjacentAlly, AdjacentAllySelf, AdjacentOpponent,
Adjacent,
AdjacentAlly,
AdjacentAllySelf,
AdjacentOpponent,
All, All, AllAdjacent, AllAdjacentOpponent, AllAlly, AllOpponent,
AllAdjacent,
AllAdjacentOpponent,
AllAlly,
AllOpponent,
Any, Any,
RandomOpponent, RandomOpponent, Self)
Self,
};
} }
#endif // CREATURELIB_ATTACKTARGET_HPP #endif // CREATURELIB_ATTACKTARGET_HPP

View File

@ -2,13 +2,14 @@
#define CREATURELIB_GENDER_HPP #define CREATURELIB_GENDER_HPP
#include <cstdint> #include <cstdint>
#include "../Core/Enum.hpp"
namespace CreatureLib::Library { namespace CreatureLib::Library {
/*! /*!
\brief Might be somewhat controversial nowadays, but as many creature battling games only have two genders, we'll \brief Might be somewhat controversial nowadays, but as many creature battling games only have two genders, we'll
hardcode those. hardcode those.
*/ */
enum class Gender : uint8_t { Male, Female, Genderless }; ENUM(Gender, uint8_t, Male, Female, Genderless)
} }
#endif // CREATURELIB_GENDER_HPP #endif // CREATURELIB_GENDER_HPP

40
tests/EnumTests.cpp Normal file
View File

@ -0,0 +1,40 @@
#ifdef TESTS_BUILD
#include <cstring>
#include "../extern/catch.hpp"
#include "../src/Core/Enum.hpp"
ENUM(TestEnum, uint8_t, Val1, Val2, Val3)
TEST_CASE("Parse Enum case sensitive", "[Utilities]") {
CHECK(TestEnumHelper::Parse("Val1") == TestEnum::Val1);
CHECK(TestEnumHelper::Parse("Val2") == TestEnum::Val2);
CHECK(TestEnumHelper::Parse("Val3") == TestEnum::Val3);
CHECK_THROWS(TestEnumHelper::Parse("Val4"));
CHECK_THROWS(TestEnumHelper::Parse("val1"));
}
TEST_CASE("Parse Enum case insensitive", "[Utilities]") {
CHECK(TestEnumHelper::Parse("Val1", true) == TestEnum::Val1);
CHECK(TestEnumHelper::Parse("Val2", true) == TestEnum::Val2);
CHECK(TestEnumHelper::Parse("Val3", true) == TestEnum::Val3);
CHECK(TestEnumHelper::Parse("val1", true) == TestEnum::Val1);
CHECK(TestEnumHelper::Parse("vAL2", true) == TestEnum::Val2);
CHECK(TestEnumHelper::Parse("VaL3", true) == TestEnum::Val3);
CHECK_THROWS(TestEnumHelper::Parse("Val4", true));
}
TEST_CASE("Enum To String", "[Utilities]") {
CHECK(TestEnumHelper::ToString(TestEnum::Val1) == "Val1");
CHECK(TestEnumHelper::ToString(TestEnum::Val2) == "Val2");
CHECK(TestEnumHelper::ToString(TestEnum::Val3) == "Val3");
}
TEST_CASE("Enum Get Values", "[Utilities]") {
auto vec = TestEnumHelper::GetValues();
REQUIRE(vec.size() == 3);
CHECK(vec[0] == TestEnum::Val1);
CHECK(vec[1] == TestEnum::Val2);
CHECK(vec[2] == TestEnum::Val3);
}
#endif