From a081d90b19ba0a696fdc13f226ae04cc43b91143 Mon Sep 17 00:00:00 2001 From: vaxerski Date: Fri, 29 Dec 2023 19:35:23 +0100 Subject: [PATCH] core: add custom data types --- include/hyprlang.hpp | 42 +++++++++-- src/common.cpp | 155 ++++++++++++++++++++++++--------------- src/config.cpp | 48 ++++++++---- src/config.hpp | 30 ++++++-- tests/config/config.conf | 2 + tests/main.cpp | 23 ++++++ 6 files changed, 217 insertions(+), 83 deletions(-) diff --git a/include/hyprlang.hpp b/include/hyprlang.hpp index aa6dc49..beef2e4 100644 --- a/include/hyprlang.hpp +++ b/include/hyprlang.hpp @@ -9,6 +9,8 @@ #include class CConfigImpl; +struct SConfigDefaultValue; +struct SSpecialCategory; namespace Hyprlang { @@ -66,8 +68,30 @@ namespace Hyprlang { /* typedefs */ typedef CParseResult (*PCONFIGHANDLERFUNC)(const char* COMMAND, const char* VALUE); + typedef CParseResult (*PCONFIGCUSTOMVALUEHANDLERFUNC)(const char* VALUE, void** data); + typedef void (*PCONFIGCUSTOMVALUEDESTRUCTOR)(void** data); + + /* Container for a custom config value type + When creating, pass your handler. + Handler will receive a void** that points to a void* that you can set to your own + thing. Pass a dtor to free whatever you allocated when the custom value type is being released. + data may always be pointing to a nullptr. + */ + class CConfigCustomValueType { + public: + CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* defaultValue); + ~CConfigCustomValueType(); + + private: + PCONFIGCUSTOMVALUEHANDLERFUNC handler = nullptr; + PCONFIGCUSTOMVALUEDESTRUCTOR dtor = nullptr; + void* data = nullptr; + std::string defaultVal = ""; + + friend class CConfigValue; + friend class CConfig; + }; - struct SConfigValueImpl; /* Container for a config value */ class CConfigValue { public: @@ -76,9 +100,11 @@ namespace Hyprlang { CConfigValue(const float value); CConfigValue(const char* value); CConfigValue(const SVector2D value); - CConfigValue(const CConfigValue&); - CConfigValue(CConfigValue&&); - void operator=(const CConfigValue&); + CConfigValue(CConfigCustomValueType&& value); + CConfigValue(const CConfigValue&) = delete; + CConfigValue(CConfigValue&&) = delete; + CConfigValue(const CConfigValue&&) = delete; + CConfigValue(CConfigValue&) = delete; ~CConfigValue(); void* dataPtr() const; @@ -89,21 +115,26 @@ namespace Hyprlang { case CONFIGDATATYPE_FLOAT: return std::any(*reinterpret_cast(m_pData)); case CONFIGDATATYPE_STR: return std::any(reinterpret_cast(m_pData)); case CONFIGDATATYPE_VEC2: return std::any(*reinterpret_cast(m_pData)); + case CONFIGDATATYPE_CUSTOM: return std::any(reinterpret_cast(m_pData)->data); default: throw; } return {}; // unreachable } private: + // remember to also edit config.hpp if editing enum eDataType { CONFIGDATATYPE_EMPTY, CONFIGDATATYPE_INT, CONFIGDATATYPE_FLOAT, CONFIGDATATYPE_STR, CONFIGDATATYPE_VEC2, + CONFIGDATATYPE_CUSTOM, }; eDataType m_eType = eDataType::CONFIGDATATYPE_EMPTY; void* m_pData = nullptr; + void defaultFrom(SConfigDefaultValue& ref); + void setFrom(std::any value); friend class CConfig; }; @@ -117,7 +148,7 @@ namespace Hyprlang { /* Add a config value, for example myCategory:myValue. This has to be done before commence() Value provided becomes default */ - void addConfigValue(const char* name, const CConfigValue value); + void addConfigValue(const char* name, const CConfigValue& value); /* Register a handler. Can be called anytime, though not recommended to do this dynamically */ @@ -181,6 +212,7 @@ namespace Hyprlang { CParseResult configSetValueSafe(const std::string& command, const std::string& value); CParseResult parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic = false); void clearState(); + void applyDefaultsToCat(SSpecialCategory& cat); }; }; #endif \ No newline at end of file diff --git a/src/common.cpp b/src/common.cpp index 01788f2..cde6db1 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1,4 +1,5 @@ #include "public.hpp" +#include "config.hpp" #include using namespace Hyprlang; @@ -16,6 +17,9 @@ void CParseResult::setError(const char* err) { } CConfigValue::~CConfigValue() { + if (m_eType == CONFIGDATATYPE_CUSTOM) + reinterpret_cast(m_pData)->dtor(&reinterpret_cast(m_pData)->data); + if (m_pData) free(m_pData); } @@ -44,65 +48,10 @@ CConfigValue::CConfigValue(const char* value) { m_eType = CONFIGDATATYPE_STR; } -CConfigValue::CConfigValue(const CConfigValue& ref) { - m_eType = ref.m_eType; - switch (ref.m_eType) { - case eDataType::CONFIGDATATYPE_INT: { - m_pData = calloc(1, sizeof(int64_t)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_FLOAT: { - m_pData = calloc(1, sizeof(float)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_VEC2: { - m_pData = calloc(1, sizeof(SVector2D)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_STR: { - auto str = std::any_cast(ref.getValue()); - m_pData = calloc(1, strlen(str) + 1); - strncpy((char*)m_pData, str, strlen(str)); - break; - } - } -} - -void CConfigValue::operator=(const CConfigValue& ref) { - m_eType = ref.m_eType; - switch (ref.m_eType) { - case eDataType::CONFIGDATATYPE_INT: { - m_pData = calloc(1, sizeof(int64_t)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_FLOAT: { - m_pData = calloc(1, sizeof(float)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_VEC2: { - m_pData = calloc(1, sizeof(SVector2D)); - *reinterpret_cast(m_pData) = std::any_cast(ref.getValue()); - break; - } - case eDataType::CONFIGDATATYPE_STR: { - auto str = std::any_cast(ref.getValue()); - m_pData = calloc(1, strlen(str) + 1); - strncpy((char*)m_pData, str, strlen(str)); - break; - } - } -} - -CConfigValue::CConfigValue(CConfigValue&& ref) { - m_pData = ref.dataPtr(); - m_eType = ref.m_eType; - ref.m_eType = eDataType::CONFIGDATATYPE_EMPTY; - ref.m_pData = nullptr; +CConfigValue::CConfigValue(CConfigCustomValueType&& value) { + m_pData = calloc(1, sizeof(CConfigCustomValueType)); + new (m_pData) CConfigCustomValueType(value); + m_eType = CONFIGDATATYPE_CUSTOM; } CConfigValue::CConfigValue() { @@ -112,3 +61,91 @@ CConfigValue::CConfigValue() { void* CConfigValue::dataPtr() const { return m_pData; } + +CConfigCustomValueType::CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* def) { + handler = handler_; + dtor = dtor_; + defaultVal = def; +} + +CConfigCustomValueType::~CConfigCustomValueType() { + dtor(&data); +} + +void CConfigValue::defaultFrom(SConfigDefaultValue& ref) { + m_eType = (CConfigValue::eDataType)ref.type; + switch (m_eType) { + case CONFIGDATATYPE_FLOAT: { + if (!m_pData) + m_pData = calloc(1, sizeof(float)); + *reinterpret_cast(m_pData) = std::any_cast(ref.data); + break; + } + case CONFIGDATATYPE_INT: { + if (!m_pData) + m_pData = calloc(1, sizeof(int64_t)); + *reinterpret_cast(m_pData) = std::any_cast(ref.data); + break; + } + case CONFIGDATATYPE_STR: { + if (!m_pData) + free(m_pData); + std::string str = std::any_cast(ref.data); + m_pData = calloc(1, str.length() + 1); + strncpy((char*)m_pData, str.c_str(), str.length()); + break; + } + case CONFIGDATATYPE_VEC2: { + if (!m_pData) + m_pData = calloc(1, sizeof(SVector2D)); + *reinterpret_cast(m_pData) = std::any_cast(ref.data); + break; + } + case CONFIGDATATYPE_CUSTOM: { + if (!m_pData) + m_pData = calloc(1, sizeof(CConfigCustomValueType)); + CConfigCustomValueType* type = reinterpret_cast(m_pData); + type->handler = ref.handler; + type->dtor = ref.dtor; + type->handler(std::any_cast(ref.data).c_str(), &type->data); + break; + } + default: { + throw "bad defaultFrom type"; + } + } +} + +void CConfigValue::setFrom(std::any value) { + switch (m_eType) { + case CONFIGDATATYPE_FLOAT: { + *reinterpret_cast(m_pData) = std::any_cast(value); + break; + } + case CONFIGDATATYPE_INT: { + *reinterpret_cast(m_pData) = std::any_cast(value); + break; + } + case CONFIGDATATYPE_STR: { + free(m_pData); + std::string str = std::any_cast(value); + m_pData = calloc(1, str.length() + 1); + strncpy((char*)m_pData, str.c_str(), str.length()); + break; + } + case CONFIGDATATYPE_VEC2: { + *reinterpret_cast(m_pData) = std::any_cast(value); + break; + } + // case CONFIGDATATYPE_CUSTOM: { + // CConfigCustomValueType* type = reinterpret_cast(m_pData); + // type->handler = ref.handler; + // type->dtor = ref.dtor; + // type->handler(std::any_cast(ref.data).c_str(), &type->data); + // break; + // } + default: { + throw "bad defaultFrom type"; + } + } +} \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index e887838..d0ec8a2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -52,11 +52,19 @@ CConfig::~CConfig() { delete impl; } -void CConfig::addConfigValue(const char* name, const CConfigValue value) { +void CConfig::addConfigValue(const char* name, const CConfigValue& value) { if (m_bCommenced) throw "Cannot addConfigValue after commence()"; - impl->defaultValues[std::string{name}] = value; + if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR) + impl->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType}); + else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR) + impl->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast(value.getValue())}, (eDataType)value.m_eType}); + else + impl->defaultValues.emplace(name, + SConfigDefaultValue{reinterpret_cast(value.m_pData)->defaultVal, (eDataType)value.m_eType, + reinterpret_cast(value.m_pData)->handler, + reinterpret_cast(value.m_pData)->dtor}); } void CConfig::addSpecialConfigValue(const char* cat, const char* name, const CConfigValue value) { @@ -65,7 +73,15 @@ void CConfig::addSpecialConfigValue(const char* cat, const char* name, const CCo if (IT == impl->specialCategoryDescriptors.end()) throw "No such category"; - IT->get()->defaultValues[std::string{name}] = value; + if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR) + IT->get()->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType}); + else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR) + IT->get()->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast(value.getValue())}, (eDataType)value.m_eType}); + else + IT->get()->defaultValues.emplace(name, + SConfigDefaultValue{reinterpret_cast(value.m_pData)->defaultVal, (eDataType)value.m_eType, + reinterpret_cast(value.m_pData)->handler, + reinterpret_cast(value.m_pData)->dtor}); } void CConfig::addSpecialCategory(const char* name, SSpecialCategoryOptions options) { @@ -85,16 +101,16 @@ void CConfig::addSpecialCategory(const char* name, SSpecialCategoryOptions optio } } -void SSpecialCategory::applyDefaults() { - for (auto& [k, v] : descriptor->defaultValues) { - values[k] = v; +void CConfig::applyDefaultsToCat(SSpecialCategory& cat) { + for (auto& [k, v] : cat.descriptor->defaultValues) { + cat.values[k].defaultFrom(v); } } void CConfig::commence() { m_bCommenced = true; for (auto& [k, v] : impl->defaultValues) { - impl->values[k] = v; + impl->values[k].defaultFrom(v); } } @@ -240,7 +256,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: PCAT->key = sc->key; addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0")); - PCAT->applyDefaults(); + applyDefaultsToCat(*PCAT); VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1)); @@ -267,7 +283,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: switch (VALUEIT->second.m_eType) { case CConfigValue::eDataType::CONFIGDATATYPE_INT: { try { - VALUEIT->second = {configStringToInt(value)}; + VALUEIT->second.setFrom(configStringToInt(value)); } catch (std::exception& e) { result.setError(std::format("failed parsing an int: {}", e.what())); return result; @@ -276,7 +292,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: } case CConfigValue::eDataType::CONFIGDATATYPE_FLOAT: { try { - VALUEIT->second = {std::stof(value)}; + VALUEIT->second.setFrom(std::stof(value)); } catch (std::exception& e) { result.setError(std::format("failed parsing a float: {}", e.what())); return result; @@ -294,7 +310,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: if (LHS.contains(" ") || RHS.contains(" ")) throw std::runtime_error("too many args"); - VALUEIT->second = {SVector2D{std::stof(LHS), std::stof(RHS)}}; + VALUEIT->second.setFrom(SVector2D{std::stof(LHS), std::stof(RHS)}); } catch (std::exception& e) { result.setError(std::format("failed parsing a vec2: {}", e.what())); return result; @@ -302,7 +318,11 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: break; } case CConfigValue::eDataType::CONFIGDATATYPE_STR: { - VALUEIT->second = {value.c_str()}; + VALUEIT->second.setFrom(value); + break; + } + case CConfigValue::eDataType::CONFIGDATATYPE_CUSTOM: { + reinterpret_cast(VALUEIT->second.m_pData)->handler(value.c_str(), &reinterpret_cast(VALUEIT->second.m_pData)->data); break; } default: { @@ -468,10 +488,10 @@ CParseResult CConfig::parse() { clearState(); for (auto& [k, v] : impl->defaultValues) { - impl->values.at(k) = v; + impl->values.at(k).defaultFrom(v); } for (auto& sc : impl->specialCategories) { - sc->applyDefaults(); + applyDefaultsToCat(*sc); } CParseResult fileParseResult = parseFile(impl->path); diff --git a/src/config.hpp b/src/config.hpp index 40c6f66..2633cec 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -16,11 +16,31 @@ struct SVariable { std::vector linesContainingVar; // for dynamic updates }; +// remember to also edit CConfigValue if editing +enum eDataType { + CONFIGDATATYPE_EMPTY, + CONFIGDATATYPE_INT, + CONFIGDATATYPE_FLOAT, + CONFIGDATATYPE_STR, + CONFIGDATATYPE_VEC2, + CONFIGDATATYPE_CUSTOM, +}; + +// CUSTOM is stored as STR!! +struct SConfigDefaultValue { + std::any data; + eDataType type = CONFIGDATATYPE_EMPTY; + + // this sucks but I have no better idea + Hyprlang::PCONFIGCUSTOMVALUEHANDLERFUNC handler = nullptr; + Hyprlang::PCONFIGCUSTOMVALUEDESTRUCTOR dtor = nullptr; +}; + struct SSpecialCategoryDescriptor { - std::string name = ""; - std::string key = ""; - std::unordered_map defaultValues; - bool dontErrorOnMissing = false; + std::string name = ""; + std::string key = ""; + std::unordered_map defaultValues; + bool dontErrorOnMissing = false; }; struct SSpecialCategory { @@ -38,7 +58,7 @@ class CConfigImpl { std::string path = ""; std::unordered_map values; - std::unordered_map defaultValues; + std::unordered_map defaultValues; std::vector handlers; std::vector variables; std::vector envVariables; diff --git a/tests/config/config.conf b/tests/config/config.conf index 4f2bd47..42a7a36 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -13,6 +13,8 @@ testEnv = $SHELL source = ./colors.conf +customType = abc + testCategory { testValueInt = 123456 testValueHex = 0xF diff --git a/tests/main.cpp b/tests/main.cpp index e8f260e..c790441 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -48,6 +48,24 @@ static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALU return pConfig->parseFile(PATH); } +static Hyprlang::CParseResult handleCustomValueSet(const char* VALUE, void** data) { + if (!*data) + *data = calloc(1, sizeof(int64_t)); + std::string V = VALUE; + if (V == "abc") + *reinterpret_cast(*data) = 1; + else + *reinterpret_cast(*data) = 2; + + Hyprlang::CParseResult result; + return result; +} + +static void handleCustomValueDestroy(void** data) { + if (*data) + free(*data); +} + int main(int argc, char** argv, char** envp) { int ret = 0; @@ -80,6 +98,7 @@ int main(int argc, char** argv, char** envp) { config.addConfigValue("myColors:pink", 0L); config.addConfigValue("myColors:green", 0L); config.addConfigValue("myColors:random", 0L); + config.addConfigValue("customType", {Hyprlang::CConfigCustomValueType{&handleCustomValueSet, &handleCustomValueDestroy, "def"}}); config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {false}); config.registerHandler(&handleFlagsTest, "flags", {true}); @@ -159,6 +178,10 @@ int main(int argc, char** argv, char** envp) { EXPECT(std::any_cast(config.getConfigValue("myColors:green")), 0xFF14f014L); EXPECT(std::any_cast(config.getConfigValue("myColors:random")), 0xFFFF1337L); + // test custom type + std::cout << " → Testing custom types\n"; + EXPECT(*reinterpret_cast(std::any_cast(config.getConfigValue("customType"))), 1L); + } catch (const char* e) { std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n"; return 1;