mirror of
https://github.com/hyprwm/hyprlang.git
synced 2024-11-16 18:25:57 +01:00
initial commit
parses variables. Progress, I guess.
This commit is contained in:
parent
3e6a554bec
commit
e15526ee91
12 changed files with 681 additions and 0 deletions
65
.clang-format
Normal file
65
.clang-format
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AlignConsecutiveAssignments: true
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: false
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: true
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
ColumnLimit: 180
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SortIncludes: false
|
||||||
|
SortUsingDeclarations: false
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Auto
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
|
||||||
|
AllowShortEnumsOnASingleLine: false
|
||||||
|
|
||||||
|
BraceWrapping:
|
||||||
|
AfterEnum: false
|
||||||
|
|
||||||
|
AlignConsecutiveDeclarations: AcrossEmptyLines
|
||||||
|
|
||||||
|
NamespaceIndentation: All
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -9,3 +9,5 @@ install_manifest.txt
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
_deps
|
_deps
|
||||||
|
.vscode
|
||||||
|
build/
|
22
CMakeLists.txt
Normal file
22
CMakeLists.txt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
project(hyprlang
|
||||||
|
VERSION "0.1"
|
||||||
|
DESCRIPTION "A library to parse hypr config files"
|
||||||
|
)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/hyprlang.hpp")
|
||||||
|
|
||||||
|
add_library(hyprlang ${SRCFILES})
|
||||||
|
target_include_directories( hyprlang
|
||||||
|
PUBLIC "./include"
|
||||||
|
PRIVATE "./src"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(hyprlang_test "tests/main.cpp")
|
||||||
|
target_link_libraries(hyprlang_test PRIVATE hyprlang)
|
||||||
|
add_test(NAME "Parsing" WORKING_DIRECTORY "./tests/" COMMAND hyprlang_test "parse")
|
10
include/hyprlang.hpp
Normal file
10
include/hyprlang.hpp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef HYPRLANG_HPP
|
||||||
|
#define HYPRLANG_HPP
|
||||||
|
|
||||||
|
#include "../src/public.hpp"
|
||||||
|
|
||||||
|
namespace Hyprlang {};
|
||||||
|
|
||||||
|
#endif
|
98
src/common.cpp
Normal file
98
src/common.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include "public.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace Hyprlang;
|
||||||
|
|
||||||
|
void CParseResult::setError(const std::string& err) {
|
||||||
|
error = true;
|
||||||
|
errorStdString = err;
|
||||||
|
errorString = errorStdString.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CParseResult::setError(const char* err) {
|
||||||
|
error = true;
|
||||||
|
errorStdString = err;
|
||||||
|
errorString = errorStdString.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfigValue::~CConfigValue() {
|
||||||
|
if (m_pData)
|
||||||
|
free(m_pData);
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfigValue::CConfigValue(const int64_t value) {
|
||||||
|
m_pData = calloc(1, sizeof(int64_t));
|
||||||
|
*reinterpret_cast<int64_t*>(m_pData) = value;
|
||||||
|
m_eType = CONFIGDATATYPE_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfigValue::CConfigValue(const float value) {
|
||||||
|
m_pData = calloc(1, sizeof(float));
|
||||||
|
*reinterpret_cast<float*>(m_pData) = value;
|
||||||
|
m_eType = CONFIGDATATYPE_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfigValue::CConfigValue(const char* value) {
|
||||||
|
m_pData = calloc(1, strlen(value) + 1);
|
||||||
|
strncpy((char*)m_pData, value, strlen(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<int64_t*>(m_pData) = std::any_cast<int64_t>(ref.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eDataType::CONFIGDATATYPE_FLOAT: {
|
||||||
|
m_pData = calloc(1, sizeof(float));
|
||||||
|
*reinterpret_cast<float*>(m_pData) = std::any_cast<float>(ref.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eDataType::CONFIGDATATYPE_STR: {
|
||||||
|
auto str = std::any_cast<const char*>(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<int64_t*>(m_pData) = std::any_cast<int64_t>(ref.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eDataType::CONFIGDATATYPE_FLOAT: {
|
||||||
|
m_pData = calloc(1, sizeof(float));
|
||||||
|
*reinterpret_cast<float*>(m_pData) = std::any_cast<float>(ref.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eDataType::CONFIGDATATYPE_STR: {
|
||||||
|
auto str = std::any_cast<const char*>(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() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* CConfigValue::dataPtr() const {
|
||||||
|
return m_pData;
|
||||||
|
}
|
276
src/config.cpp
Normal file
276
src/config.cpp
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
#include "config.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace Hyprlang;
|
||||||
|
|
||||||
|
static std::string removeBeginEndSpacesTabs(std::string str) {
|
||||||
|
if (str.empty())
|
||||||
|
return str;
|
||||||
|
|
||||||
|
int countBefore = 0;
|
||||||
|
while (str[countBefore] == ' ' || str[countBefore] == '\t') {
|
||||||
|
countBefore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int countAfter = 0;
|
||||||
|
while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
|
||||||
|
countAfter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substr(countBefore, str.length() - countBefore - countAfter);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfig::CConfig(const char* path) {
|
||||||
|
impl = new CConfigImpl;
|
||||||
|
try {
|
||||||
|
impl->path = std::filesystem::canonical(path);
|
||||||
|
} catch (std::exception& e) { throw "Couldn't open file. File does not exist"; }
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(impl->path))
|
||||||
|
throw "File does not exist";
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfig::~CConfig() {
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConfig::addConfigValue(const char* name, const CConfigValue value) {
|
||||||
|
if (m_bCommenced)
|
||||||
|
throw "Cannot addConfigValue after commence()";
|
||||||
|
|
||||||
|
impl->defaultValues[std::string{name}] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConfig::commence() {
|
||||||
|
m_bCommenced = true;
|
||||||
|
for (auto& [k, v] : impl->defaultValues) {
|
||||||
|
impl->values[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNumber(const std::string& str, bool allowfloat) {
|
||||||
|
|
||||||
|
std::string copy = str;
|
||||||
|
if (*copy.begin() == '-')
|
||||||
|
copy = copy.substr(1);
|
||||||
|
|
||||||
|
if (copy.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool point = !allowfloat;
|
||||||
|
for (auto& c : copy) {
|
||||||
|
if (c == '.') {
|
||||||
|
if (point)
|
||||||
|
return false;
|
||||||
|
point = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isdigit(c))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t configStringToInt(const std::string& VALUE) {
|
||||||
|
if (VALUE.starts_with("0x")) {
|
||||||
|
// Values with 0x are hex
|
||||||
|
const auto VALUEWITHOUTHEX = VALUE.substr(2);
|
||||||
|
return stol(VALUEWITHOUTHEX, nullptr, 16);
|
||||||
|
} else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
|
||||||
|
const auto VALUEWITHOUTFUNC = VALUE.substr(5, VALUE.length() - 6);
|
||||||
|
|
||||||
|
if (removeBeginEndSpacesTabs(VALUEWITHOUTFUNC).length() != 8) {
|
||||||
|
throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto RGBA = std::stol(VALUEWITHOUTFUNC, nullptr, 16);
|
||||||
|
|
||||||
|
// now we need to RGBA -> ARGB. The config holds ARGB only.
|
||||||
|
return (RGBA >> 8) + 0x1000000 * (RGBA & 0xFF);
|
||||||
|
} else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
|
||||||
|
const auto VALUEWITHOUTFUNC = VALUE.substr(4, VALUE.length() - 5);
|
||||||
|
|
||||||
|
if (removeBeginEndSpacesTabs(VALUEWITHOUTFUNC).length() != 6) {
|
||||||
|
throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto RGB = std::stol(VALUEWITHOUTFUNC, nullptr, 16);
|
||||||
|
|
||||||
|
return RGB + 0xFF000000; // 0xFF for opaque
|
||||||
|
} else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) {
|
||||||
|
return 1;
|
||||||
|
} else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VALUE.empty() || !isNumber(VALUE, false))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return std::stoll(VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CParseResult CConfig::configSetValueSafe(const std::string& command, const std::string& value) {
|
||||||
|
CParseResult result;
|
||||||
|
|
||||||
|
std::string valueName;
|
||||||
|
for (auto& c : impl->categories) {
|
||||||
|
valueName += c + ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
valueName += command;
|
||||||
|
|
||||||
|
const auto VALUEIT = impl->values.find(valueName);
|
||||||
|
if (VALUEIT == impl->values.end()) {
|
||||||
|
result.setError("config option doesn't exist");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (VALUEIT->second.m_eType) {
|
||||||
|
case CConfigValue::eDataType::CONFIGDATATYPE_INT: {
|
||||||
|
try {
|
||||||
|
VALUEIT->second = {configStringToInt(value)};
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
result.setError(std::format("failed parsing an int: {}", e.what()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CConfigValue::eDataType::CONFIGDATATYPE_FLOAT: {
|
||||||
|
try {
|
||||||
|
VALUEIT->second = {std::stof(value)};
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
result.setError(std::format("failed parsing a float: {}", e.what()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CConfigValue::eDataType::CONFIGDATATYPE_STR: {
|
||||||
|
VALUEIT->second = {value.c_str()};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
result.setError("internal error: invalid value found (no type?)");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CParseResult CConfig::parseLine(std::string line) {
|
||||||
|
CParseResult result;
|
||||||
|
|
||||||
|
auto commentPos = line.find('#');
|
||||||
|
size_t lastHashPos = 0;
|
||||||
|
|
||||||
|
while (commentPos != std::string::npos) {
|
||||||
|
bool escaped = false;
|
||||||
|
if (commentPos < line.length() - 1) {
|
||||||
|
if (line[commentPos + 1] == '#') {
|
||||||
|
lastHashPos = commentPos + 2;
|
||||||
|
escaped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!escaped) {
|
||||||
|
line = line.substr(0, commentPos);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
commentPos = line.find('#', lastHashPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line = removeBeginEndSpacesTabs(line);
|
||||||
|
|
||||||
|
auto equalsPos = line.find('=');
|
||||||
|
|
||||||
|
if (equalsPos == std::string::npos && !line.ends_with("{") && line != "}" && !line.empty()) {
|
||||||
|
// invalid line
|
||||||
|
result.setError("Invalid config line");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equalsPos != std::string::npos) {
|
||||||
|
// set value
|
||||||
|
CParseResult ret = configSetValueSafe(removeBeginEndSpacesTabs(line.substr(0, equalsPos)), removeBeginEndSpacesTabs(line.substr(equalsPos + 1)));
|
||||||
|
if (ret.error) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else if (!line.empty()) {
|
||||||
|
// has to be a set
|
||||||
|
if (line.contains("}")) {
|
||||||
|
// easiest. } or invalid.
|
||||||
|
if (line != "}") {
|
||||||
|
result.setError("Invalid config line");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (impl->categories.empty()) {
|
||||||
|
result.setError("Stray category close");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->categories.pop_back();
|
||||||
|
} else {
|
||||||
|
// open a category.
|
||||||
|
if (!line.ends_with("{")) {
|
||||||
|
result.setError("Invalid category open, garbage after {");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.pop_back();
|
||||||
|
line = removeBeginEndSpacesTabs(line);
|
||||||
|
impl->categories.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CParseResult CConfig::parse() {
|
||||||
|
if (!m_bCommenced)
|
||||||
|
throw "Cannot parse: not commenced. You have to .commence() first.";
|
||||||
|
|
||||||
|
impl->parseError = "";
|
||||||
|
|
||||||
|
for (auto& [k, v] : impl->defaultValues) {
|
||||||
|
impl->values.at(k) = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream iffile(impl->path);
|
||||||
|
if (!iffile.good())
|
||||||
|
throw "Config file failed to open";
|
||||||
|
|
||||||
|
std::string line = "";
|
||||||
|
int linenum = 1;
|
||||||
|
|
||||||
|
CParseResult fileParseResult;
|
||||||
|
|
||||||
|
while (std::getline(iffile, line)) {
|
||||||
|
|
||||||
|
const auto RET = parseLine(line);
|
||||||
|
|
||||||
|
if (RET.error && impl->parseError.empty()) {
|
||||||
|
impl->parseError = RET.getError();
|
||||||
|
fileParseResult.setError(std::format("Config error at line {}: {}", linenum, RET.errorStdString));
|
||||||
|
}
|
||||||
|
|
||||||
|
++linenum;
|
||||||
|
}
|
||||||
|
|
||||||
|
iffile.close();
|
||||||
|
|
||||||
|
return fileParseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
CConfigValue* CConfig::getConfigValuePtr(const char* name) {
|
||||||
|
const auto IT = impl->values.find(std::string{name});
|
||||||
|
return IT == impl->values.end() ? nullptr : &IT->second;
|
||||||
|
}
|
17
src/config.hpp
Normal file
17
src/config.hpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "public.hpp"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class CConfigImpl {
|
||||||
|
public:
|
||||||
|
std::string path = "";
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Hyprlang::CConfigValue> values;
|
||||||
|
std::unordered_map<std::string, Hyprlang::CConfigValue> defaultValues;
|
||||||
|
|
||||||
|
std::vector<std::string> categories;
|
||||||
|
|
||||||
|
std::string parseError = "";
|
||||||
|
};
|
1
src/core.hpp
Normal file
1
src/core.hpp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#pragma once
|
3
src/logger.hpp
Normal file
3
src/logger.hpp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Logger {}
|
103
src/public.hpp
Normal file
103
src/public.hpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#pragma once
|
||||||
|
#include <any>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class CConfigImpl;
|
||||||
|
|
||||||
|
namespace Hyprlang {
|
||||||
|
|
||||||
|
struct SConfigValueImpl;
|
||||||
|
/* Container for a config value */
|
||||||
|
class CConfigValue {
|
||||||
|
public:
|
||||||
|
CConfigValue();
|
||||||
|
CConfigValue(const int64_t value);
|
||||||
|
CConfigValue(const float value);
|
||||||
|
CConfigValue(const char* value);
|
||||||
|
CConfigValue(const CConfigValue&);
|
||||||
|
CConfigValue(CConfigValue&&);
|
||||||
|
void operator=(const CConfigValue&);
|
||||||
|
~CConfigValue();
|
||||||
|
|
||||||
|
void* dataPtr() const;
|
||||||
|
std::any getValue() const {
|
||||||
|
switch (m_eType) {
|
||||||
|
case CONFIGDATATYPE_EMPTY: throw;
|
||||||
|
case CONFIGDATATYPE_INT: return std::any(*reinterpret_cast<int64_t*>(m_pData));
|
||||||
|
case CONFIGDATATYPE_FLOAT: return std::any(*reinterpret_cast<float*>(m_pData));
|
||||||
|
case CONFIGDATATYPE_STR: return std::any(reinterpret_cast<const char*>(m_pData));
|
||||||
|
default: throw;
|
||||||
|
}
|
||||||
|
return {}; // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum eDataType {
|
||||||
|
CONFIGDATATYPE_EMPTY,
|
||||||
|
CONFIGDATATYPE_INT,
|
||||||
|
CONFIGDATATYPE_FLOAT,
|
||||||
|
CONFIGDATATYPE_STR,
|
||||||
|
};
|
||||||
|
eDataType m_eType = eDataType::CONFIGDATATYPE_EMPTY;
|
||||||
|
void* m_pData = nullptr;
|
||||||
|
|
||||||
|
friend class CConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CParseResult {
|
||||||
|
public:
|
||||||
|
bool error = false;
|
||||||
|
const char* getError() const {
|
||||||
|
return errorString;
|
||||||
|
}
|
||||||
|
void setError(const char* err);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setError(const std::string& err);
|
||||||
|
|
||||||
|
std::string errorStdString = "";
|
||||||
|
const char* errorString = nullptr;
|
||||||
|
|
||||||
|
friend class CConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Base class for a config file */
|
||||||
|
class CConfig {
|
||||||
|
public:
|
||||||
|
CConfig(const char* configPath);
|
||||||
|
~CConfig();
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* Commence the config state. Config becomes immutable, as in
|
||||||
|
no new values may be added or removed. Required for parsing. */
|
||||||
|
void commence();
|
||||||
|
|
||||||
|
/* Parse the config. Refresh the values. */
|
||||||
|
CParseResult parse();
|
||||||
|
|
||||||
|
/* Get a config's value ptr. These are static.
|
||||||
|
nullptr on fail */
|
||||||
|
CConfigValue* getConfigValuePtr(const char* name);
|
||||||
|
|
||||||
|
/* Get a config value's stored value. Empty on fail*/
|
||||||
|
std::any getConfigValue(const char* name) {
|
||||||
|
CConfigValue* val = getConfigValuePtr(name);
|
||||||
|
if (!val)
|
||||||
|
return {};
|
||||||
|
return val->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_bCommenced = false;
|
||||||
|
|
||||||
|
CConfigImpl* impl;
|
||||||
|
|
||||||
|
CParseResult parseLine(std::string line);
|
||||||
|
CParseResult configSetValueSafe(const std::string& command, const std::string& value);
|
||||||
|
};
|
||||||
|
};
|
26
tests/config/config.conf
Normal file
26
tests/config/config.conf
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
# Test comment
|
||||||
|
|
||||||
|
testInt = 123
|
||||||
|
testFloat = 123.456
|
||||||
|
testString = Hello World! ## This is not a comment! # This is!
|
||||||
|
|
||||||
|
testCategory {
|
||||||
|
testValueInt = 123456
|
||||||
|
testValueHex = 0xFFFFaabb
|
||||||
|
|
||||||
|
nested1 {
|
||||||
|
testValueNest = 1
|
||||||
|
nested2 {
|
||||||
|
testValueNest = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testStringQuotes = "Hello World!"
|
||||||
|
#testDefault = 123
|
||||||
|
|
||||||
|
#doABarrelRoll = woohoo, some, params # Funny!
|
||||||
|
#doSomethingFunny = 1, 2, 3, 4 # Funnier!
|
||||||
|
#testSpaces = abc , def # many spaces, should be trimmed
|
||||||
|
|
58
tests/main.cpp
Normal file
58
tests/main.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <hyprlang.hpp>
|
||||||
|
|
||||||
|
#define EXPECT(expr, val) \
|
||||||
|
if (const auto RESULT = expr; RESULT != (val)) { \
|
||||||
|
std::cout << "Failed: " << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \
|
||||||
|
ret = 1; \
|
||||||
|
} else { \
|
||||||
|
std::cout << "Passed " << #expr << ". Got " << #val << "\n"; \
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv, char** envp) {
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::cout << "Starting test\n";
|
||||||
|
|
||||||
|
Hyprlang::CConfig config("./config/config.conf");
|
||||||
|
|
||||||
|
// setup config
|
||||||
|
config.addConfigValue("testInt", 0L);
|
||||||
|
config.addConfigValue("testFloat", 0.F);
|
||||||
|
config.addConfigValue("testString", "");
|
||||||
|
config.addConfigValue("testStringQuotes", "");
|
||||||
|
config.addConfigValue("testCategory:testValueInt", 0L);
|
||||||
|
config.addConfigValue("testCategory:testValueHex", 0xAL);
|
||||||
|
config.addConfigValue("testCategory:nested1:testValueNest", 0L);
|
||||||
|
config.addConfigValue("testCategory:nested1:nested2:testValueNest", 0L);
|
||||||
|
config.addConfigValue("testDefault", 123L);
|
||||||
|
|
||||||
|
config.commence();
|
||||||
|
|
||||||
|
const auto PARSERESULT = config.parse();
|
||||||
|
if (PARSERESULT.error) {
|
||||||
|
std::cout << "Parse error: " << PARSERESULT.getError() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT(PARSERESULT.error, false);
|
||||||
|
|
||||||
|
// test values
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testInt")), 123);
|
||||||
|
EXPECT(std::any_cast<float>(config.getConfigValue("testFloat")), 123.456f);
|
||||||
|
EXPECT(std::any_cast<const char*>(config.getConfigValue("testString")), std::string{"Hello World! ## This is not a comment!"});
|
||||||
|
EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringQuotes")), std::string{"\"Hello World!\""});
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testCategory:testValueInt")), 123456L);
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testCategory:testValueHex")), 0xFFFFAABBL);
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testCategory:nested1:testValueNest")), 1L);
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testCategory:nested1:nested2:testValueNest")), 1L);
|
||||||
|
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testDefault")), 123L);
|
||||||
|
} catch (const char* e) {
|
||||||
|
std::cout << "Error: " << e << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
Loading…
Reference in a new issue