mirror of
https://github.com/mariiaan/minipp.git
synced 2026-05-14 10:11:17 +02:00
Implement the parser and writer
This commit is contained in:
2
minipp/DoubleFileTest.cpp
Normal file
2
minipp/DoubleFileTest.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
#include "minipp/minipp.hpp"
|
||||||
@@ -130,8 +130,8 @@
|
|||||||
<ClInclude Include="minipp\minipp.hpp" />
|
<ClInclude Include="minipp\minipp.hpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="DoubleFileTest.cpp" />
|
||||||
<ClCompile Include="test.cpp" />
|
<ClCompile Include="test.cpp" />
|
||||||
<ClCompile Include="minipp\minipp.cpp" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
</ClInclude>
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="minipp\minipp.cpp">
|
<ClCompile Include="test.cpp">
|
||||||
<Filter>Quelldateien</Filter>
|
<Filter>Quelldateien</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="test.cpp">
|
<ClCompile Include="DoubleFileTest.cpp">
|
||||||
<Filter>Quelldateien</Filter>
|
<Filter>Quelldateien</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,436 +0,0 @@
|
|||||||
#include "minipp.hpp"
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#define MINIPP_ENABLE_DEBUG_OUTPUT true
|
|
||||||
|
|
||||||
#if MINIPP_ENABLE_DEBUG_OUTPUT
|
|
||||||
#include <iostream>
|
|
||||||
#define COUT(msg) std::cout << msg << std::endl
|
|
||||||
#else
|
|
||||||
#define COUT(msg)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ASSERT(condition) if (!condition) abort();
|
|
||||||
|
|
||||||
#define COUT_SYNTAX_ERROR(line, msg) COUT("Error in line " << line << ": " << msg)
|
|
||||||
|
|
||||||
minipp::MiniPPFile::Section::~Section()
|
|
||||||
{
|
|
||||||
for (auto& pair : m_values)
|
|
||||||
delete pair.second;
|
|
||||||
for (auto& pair : m_subSections)
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::MiniPPFile::Section* minipp::MiniPPFile::Section::GetSubSection(const std::string& key, EResult* result) const noexcept
|
|
||||||
{
|
|
||||||
auto pathIndex = key.find('.');
|
|
||||||
std::string thisKey = key;
|
|
||||||
std::string rest;
|
|
||||||
if (pathIndex != std::string::npos)
|
|
||||||
{
|
|
||||||
thisKey = key.substr(0, pathIndex);
|
|
||||||
rest = key.substr(pathIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = m_subSections.find(thisKey);
|
|
||||||
if (it == m_subSections.end())
|
|
||||||
{
|
|
||||||
COUT("Sub-Section not found: " << thisKey);
|
|
||||||
if (result != nullptr)
|
|
||||||
*result = EResult::SectionNotPresent;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (result != nullptr)
|
|
||||||
*result = EResult::Success;
|
|
||||||
if (rest.empty())
|
|
||||||
return it->second;
|
|
||||||
return it->second->GetSubSection(rest, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::MiniPPFile::Section* minipp::MiniPPFile::Section::SetSubSection(const std::string& name, std::unique_ptr<Section> value, bool allowOverwrite, EResult* result) noexcept
|
|
||||||
{
|
|
||||||
if (m_subSections.find(name) != m_subSections.end())
|
|
||||||
if (!allowOverwrite)
|
|
||||||
{
|
|
||||||
if (result != nullptr)
|
|
||||||
*result = EResult::SectionAlreadyPresent;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
delete m_subSections[name];
|
|
||||||
|
|
||||||
auto val = value.release();
|
|
||||||
m_subSections[name] = val;
|
|
||||||
if (result != nullptr)
|
|
||||||
*result = EResult::Success;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::MiniPPFile::StringValue::StringValue(const std::string& str) : m_value(str) {}
|
|
||||||
|
|
||||||
minipp::EResult minipp::MiniPPFile::StringValue::Parse(const std::string& str) noexcept
|
|
||||||
{
|
|
||||||
m_value = "";
|
|
||||||
for (size_t i = 0; i < str.size(); ++i)
|
|
||||||
{
|
|
||||||
if (str[i] == '\\')
|
|
||||||
{
|
|
||||||
if (i + 1 >= str.size())
|
|
||||||
return EResult::FormatError;
|
|
||||||
|
|
||||||
switch (str[i + 1])
|
|
||||||
{
|
|
||||||
case '\"':
|
|
||||||
m_value.push_back('\"');
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
m_value.push_back('\n');
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
m_value.push_back('\t');
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
m_value.push_back('\r');
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
m_value.push_back('\\');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else if (str[i] == '"')
|
|
||||||
return EResult::FormatError;
|
|
||||||
else
|
|
||||||
m_value.push_back(str[i]);
|
|
||||||
}
|
|
||||||
return EResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::EResult minipp::MiniPPFile::StringValue::ToString(std::string& destination) const noexcept
|
|
||||||
{
|
|
||||||
std::string sanitizedValue = m_value;
|
|
||||||
for (size_t i = 0; i < sanitizedValue.size(); ++i)
|
|
||||||
{
|
|
||||||
switch (sanitizedValue[i])
|
|
||||||
{
|
|
||||||
case '\n':
|
|
||||||
sanitizedValue.replace(i, 1, "\\n");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '\t':
|
|
||||||
sanitizedValue.replace(i, 1, "\\t");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
sanitizedValue.replace(i, 1, "\\r");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
sanitizedValue.insert(i, 1, '\\');
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '\"':
|
|
||||||
sanitizedValue.insert(i, 1, '\\');
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destination = "\"" + sanitizedValue + "\"";
|
|
||||||
return EResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& minipp::MiniPPFile::StringValue::GetValue() const noexcept
|
|
||||||
{
|
|
||||||
return m_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept
|
|
||||||
{
|
|
||||||
std::ifstream ifs;
|
|
||||||
ifs.open(path);
|
|
||||||
if (!ifs.is_open())
|
|
||||||
return EResult::FileIOError;
|
|
||||||
|
|
||||||
int64_t lineCounter = 0;
|
|
||||||
|
|
||||||
Section* currentSection = nullptr;
|
|
||||||
|
|
||||||
std::string currentLine;
|
|
||||||
while (std::getline(ifs, currentLine))
|
|
||||||
{
|
|
||||||
++lineCounter;
|
|
||||||
Tools::StringTrim(currentLine);
|
|
||||||
if (currentLine.empty())
|
|
||||||
continue;
|
|
||||||
char firstChar = currentLine[0];
|
|
||||||
char lastChar = currentLine[currentLine.size() - 1];
|
|
||||||
|
|
||||||
if (firstChar == '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (firstChar == '[')
|
|
||||||
{
|
|
||||||
if (lastChar != ']')
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
std::string sectionName = currentLine.substr(1, currentLine.size() - 2);
|
|
||||||
Tools::StringTrim(sectionName);
|
|
||||||
if (sectionName.empty())
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
// Create section tree
|
|
||||||
Section* ubSection = &m_rootSection;
|
|
||||||
|
|
||||||
std::vector<std::string> sectionPath = Tools::SplitByDelimiter(sectionName, '.');
|
|
||||||
for (size_t i = 0; i < sectionPath.size(); ++i)
|
|
||||||
{
|
|
||||||
const std::string& sectionName = sectionPath[i];
|
|
||||||
if (!Tools::IsNameValid(sectionName))
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
|
|
||||||
EResult result;
|
|
||||||
ubSection->SetSubSection(sectionName, std::make_unique<Section>(), false, &result); // if this fails, the section is already present
|
|
||||||
if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1)
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
ubSection = ubSection->GetSubSection(sectionName, &result);
|
|
||||||
ASSERT(Tools::IsResultOk(result)); // should never happen
|
|
||||||
}
|
|
||||||
currentSection = ubSection;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (currentSection == nullptr)
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '=');
|
|
||||||
if (keyValueDelimiterIndex == -1)
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex);
|
|
||||||
Tools::StringTrim(keyValuePair.first);
|
|
||||||
Tools::StringTrim(keyValuePair.second);
|
|
||||||
if (keyValuePair.first.empty())
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected key in line.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
if (keyValuePair.second.empty())
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
std::string& value = keyValuePair.second;
|
|
||||||
char valueFirstChar = value.front();
|
|
||||||
char valueLastChar = value.back();
|
|
||||||
if (valueFirstChar == '"')
|
|
||||||
{
|
|
||||||
if (valueLastChar != '"')
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Expected '\"' at the end of the value.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
// is string
|
|
||||||
value = value.substr(1, value.size() - 2);
|
|
||||||
StringValue strValue;
|
|
||||||
auto parseResult = strValue.Parse(value);
|
|
||||||
if (!Tools::IsResultOk(parseResult))
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Invalid string value.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
currentSection->SetValue(keyValuePair.first, strValue, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IntValue intValue;
|
|
||||||
auto parseResult = intValue.Parse(value);
|
|
||||||
if (!Tools::IsResultOk(parseResult))
|
|
||||||
{
|
|
||||||
COUT_SYNTAX_ERROR(lineCounter, "Invalid integer value.");
|
|
||||||
return EResult::FormatError;
|
|
||||||
}
|
|
||||||
currentSection->SetValue(keyValuePair.first, intValue, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return EResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void minipp::MiniPPFile::Write(const std::string& path) noexcept
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool minipp::MiniPPFile::Tools::StringStartsWith(const std::string& str, const std::string& beg)
|
|
||||||
{
|
|
||||||
if (str.size() < beg.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < beg.size(); ++i)
|
|
||||||
if (str[i] != beg[i])
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool minipp::MiniPPFile::Tools::StringEndsWith(const std::string& str, const std::string& end)
|
|
||||||
{
|
|
||||||
if (str.size() < end.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < end.size(); ++i)
|
|
||||||
if (str[str.size() - i - 1] != end[end.size() - i - 1])
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void minipp::MiniPPFile::Tools::StringTrim(std::string& str)
|
|
||||||
{
|
|
||||||
if (str.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
size_t start = 0;
|
|
||||||
size_t end = str.size() - 1;
|
|
||||||
while (str[start] == ' ' || str[start] == '\t')
|
|
||||||
start++;
|
|
||||||
while (str[end] == ' ' || str[end] == '\t')
|
|
||||||
end--;
|
|
||||||
|
|
||||||
str = str.substr(start, end - start + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool minipp::MiniPPFile::Tools::IsNameValid(const std::string& name) noexcept
|
|
||||||
{
|
|
||||||
for (const char c : name)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
(c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= 'A' && c <= 'Z') ||
|
|
||||||
(c >= '0' && c <= '9') ||
|
|
||||||
(c == '_'))
|
|
||||||
continue;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t minipp::MiniPPFile::Tools::FirstIndexOf(const std::string& str, char c) noexcept
|
|
||||||
{
|
|
||||||
int64_t index = 0;
|
|
||||||
for (const char ch : str)
|
|
||||||
{
|
|
||||||
if (ch == c)
|
|
||||||
return index;
|
|
||||||
++index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t minipp::MiniPPFile::Tools::LastIndexOf(const std::string& str, char c) noexcept
|
|
||||||
{
|
|
||||||
int64_t index = str.size() - 1;
|
|
||||||
for (int64_t i = str.size() - 1; i >= 0; --i)
|
|
||||||
{
|
|
||||||
if (str[i] == c)
|
|
||||||
return index;
|
|
||||||
--index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::string, std::string> minipp::MiniPPFile::Tools::SplitInTwo(const std::string& str, int64_t firstLength) noexcept
|
|
||||||
{
|
|
||||||
return std::make_pair(str.substr(0, firstLength), str.substr(firstLength + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept
|
|
||||||
{
|
|
||||||
std::vector<std::string> elements;
|
|
||||||
std::string tmp;
|
|
||||||
|
|
||||||
for (const char c : str)
|
|
||||||
{
|
|
||||||
if (c == delimiter)
|
|
||||||
{
|
|
||||||
elements.push_back(tmp);
|
|
||||||
tmp.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
tmp.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tmp.empty())
|
|
||||||
elements.push_back(tmp);
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool minipp::MiniPPFile::Tools::IsResultOk(EResult result) noexcept
|
|
||||||
{
|
|
||||||
return static_cast<int32_t>(result) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void minipp::MiniPPFile::Tools::RemoveAll(std::string& str, char old)
|
|
||||||
{
|
|
||||||
str.erase(std::remove(str.begin(), str.end(), old), str.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool minipp::MiniPPFile::Tools::IsIntegerDecimal(const std::string& str) noexcept
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < str.size(); ++i)
|
|
||||||
if (str[i] < '0' || str[i] > '9')
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::MiniPPFile::IntValue::IntValue(int64_t value) : m_value(value) {}
|
|
||||||
|
|
||||||
minipp::EResult minipp::MiniPPFile::IntValue::Parse(const std::string& str) noexcept
|
|
||||||
{
|
|
||||||
std::string sanitizedValue = str;
|
|
||||||
Tools::RemoveAll(sanitizedValue, '_');
|
|
||||||
if (sanitizedValue.empty())
|
|
||||||
return EResult::FormatError;
|
|
||||||
|
|
||||||
char lastCharacter = str.back();
|
|
||||||
auto rest = str.substr(0, str.size() - 1);
|
|
||||||
if (lastCharacter == 'h')
|
|
||||||
m_value = std::stoll(rest, nullptr, 16);
|
|
||||||
else if (lastCharacter == 'b')
|
|
||||||
m_value = std::stoll(rest, nullptr, 2);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!Tools::IsIntegerDecimal(sanitizedValue))
|
|
||||||
return EResult::FormatError;
|
|
||||||
m_value = std::stoll(sanitizedValue);
|
|
||||||
}
|
|
||||||
return EResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
minipp::EResult minipp::MiniPPFile::IntValue::ToString(std::string& destination) const noexcept
|
|
||||||
{
|
|
||||||
destination = std::to_string(m_value);
|
|
||||||
return EResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t minipp::MiniPPFile::IntValue::GetValue() const noexcept
|
|
||||||
{
|
|
||||||
return m_value;
|
|
||||||
}
|
|
||||||
@@ -6,83 +6,147 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define MINIPP_ENABLE_DEBUG_OUTPUT true
|
||||||
|
|
||||||
|
// define MINIPP_IMPLEMENTATION in one single translation unit before including this header!
|
||||||
|
|
||||||
namespace minipp
|
namespace minipp
|
||||||
{
|
{
|
||||||
enum class EResult
|
enum class EResult
|
||||||
{
|
{
|
||||||
/* Errors */
|
/* Errors */
|
||||||
KeyNotPresent = -1,
|
KeyNotPresent = -1,
|
||||||
KeyAlreadyPresent = -2,
|
KeyAlreadyPresent = -2,
|
||||||
SectionNotPresent = -3,
|
SectionNotPresent = -3,
|
||||||
SectionAlreadyPresent = -4,
|
SectionAlreadyPresent = -4,
|
||||||
FileIOError = -5,
|
FileIOError = -5,
|
||||||
InvalidDataType = -6,
|
InvalidDataType = -6,
|
||||||
FormatError = -7,
|
FormatError = -7,
|
||||||
|
ArrayDataTypeInconsitency = -8,
|
||||||
|
|
||||||
/* OK Codes*/
|
/* OK Codes*/
|
||||||
Success = 1,
|
Success = +1,
|
||||||
ValueOverwritten,
|
ValueOverwritten = +2
|
||||||
ValueOverwrittenAndDataTypeChanged
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class EIntStyle
|
||||||
|
{
|
||||||
|
Decimal,
|
||||||
|
Hexadecimal,
|
||||||
|
Binary
|
||||||
|
};
|
||||||
|
|
||||||
class MiniPPFile
|
class MiniPPFile
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
class Tools
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static bool StringStartsWith(const std::string& str, const std::string& beg);
|
|
||||||
static bool StringEndsWith(const std::string& str, const std::string& end);
|
|
||||||
static void StringTrim(std::string& str);
|
|
||||||
static bool IsNameValid(const std::string& name) noexcept;
|
|
||||||
static int64_t FirstIndexOf(const std::string& str, char c) noexcept;
|
|
||||||
static int64_t LastIndexOf(const std::string& str, char c) noexcept;
|
|
||||||
static std::pair<std::string, std::string> SplitInTwo(const std::string& str, int64_t firstLength) noexcept;
|
|
||||||
static std::vector<std::string> SplitByDelimiter(const std::string& str, char delimiter) noexcept;
|
|
||||||
static bool IsResultOk(EResult result) noexcept;
|
|
||||||
static void RemoveAll(std::string& str, char old);
|
|
||||||
static bool IsIntegerDecimal(const std::string& str) noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class Value
|
class Value
|
||||||
{
|
{
|
||||||
|
friend class MiniPPFile;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::string> m_comments;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual EResult Parse(const std::string& str) noexcept = 0;
|
virtual EResult Parse(const std::string& str) noexcept = 0;
|
||||||
virtual EResult ToString(std::string& destination) const noexcept = 0;
|
virtual EResult ToString(std::string& destination) const noexcept = 0;
|
||||||
|
virtual ~Value() = default;
|
||||||
|
std::vector<std::string>& GetComments() noexcept { return m_comments; }
|
||||||
|
const std::vector<std::string>& GetComments() const noexcept { return m_comments; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringValue : public Value
|
class Values
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
std::string m_value;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StringValue() = default;
|
class StringValue : public Value
|
||||||
StringValue(const std::string& str);
|
{
|
||||||
EResult Parse(const std::string& str) noexcept override;
|
private:
|
||||||
EResult ToString(std::string& destination) const noexcept override;
|
std::string m_value;
|
||||||
const std::string& GetValue() const noexcept;
|
|
||||||
|
public:
|
||||||
|
StringValue() = default;
|
||||||
|
StringValue(const std::string& str);
|
||||||
|
EResult Parse(const std::string& str) noexcept override;
|
||||||
|
EResult ToString(std::string& destination) const noexcept override;
|
||||||
|
const std::string& GetValue() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntValue : public Value
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int64_t m_value = 0;
|
||||||
|
EIntStyle m_style = EIntStyle::Decimal;
|
||||||
|
|
||||||
|
public:
|
||||||
|
IntValue() = default;
|
||||||
|
IntValue(int64_t value);
|
||||||
|
EResult Parse(const std::string& str) noexcept override;
|
||||||
|
EResult ToString(std::string& destination) const noexcept override;
|
||||||
|
int64_t GetValue() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BooleanValue : public Value
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bool m_value = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BooleanValue() = default;
|
||||||
|
BooleanValue(bool value);
|
||||||
|
EResult Parse(const std::string& str) noexcept override;
|
||||||
|
EResult ToString(std::string& destination) const noexcept override;
|
||||||
|
bool GetValue() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FloatValue : public Value
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
double m_value = 0.0f;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FloatValue() = default;
|
||||||
|
FloatValue(double value);
|
||||||
|
EResult Parse(const std::string& str) noexcept override;
|
||||||
|
EResult ToString(std::string& destination) const noexcept override;
|
||||||
|
double GetValue() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArrayValue : public Value
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<Value>> m_values;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArrayValue() = default;
|
||||||
|
ArrayValue(const ArrayValue&) = delete;
|
||||||
|
ArrayValue& operator=(const ArrayValue&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EResult Parse(const std::string& str) noexcept override;
|
||||||
|
EResult ToString(std::string& destination) const noexcept override;
|
||||||
|
std::vector<std::unique_ptr<Value>>& GetValues() noexcept { return m_values; }
|
||||||
|
const std::vector<std::unique_ptr<Value>>& GetValues() const noexcept { return m_values; }
|
||||||
|
|
||||||
|
Value* operator[](size_t index) noexcept
|
||||||
|
{
|
||||||
|
if (index >= m_values.size())
|
||||||
|
return nullptr;
|
||||||
|
return m_values[index].get();
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntValue : public Value
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
int64_t m_value = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
IntValue() = default;
|
|
||||||
IntValue(int64_t value);
|
|
||||||
EResult Parse(const std::string& str) noexcept override;
|
|
||||||
EResult ToString(std::string& destination) const noexcept override;
|
|
||||||
int64_t GetValue() const noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Section
|
class Section
|
||||||
{
|
{
|
||||||
|
friend class MiniPPFile;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, Value*> m_values;
|
std::unordered_map<std::string, Value*> m_values;
|
||||||
std::unordered_map<std::string, Section*> m_subSections;
|
std::unordered_map<std::string, Section*> m_subSections;
|
||||||
|
std::vector<std::string> m_comments;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<std::string>& GetComments() noexcept { return m_comments; }
|
||||||
|
const std::vector<std::string>& GetComments() const noexcept { return m_comments; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Section() = default;
|
Section() = default;
|
||||||
@@ -92,7 +156,7 @@ namespace minipp
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
template<typename T>
|
template<typename T>
|
||||||
EResult GetValue(T** target, const std::string& key) noexcept
|
EResult GetValue(const std::string& key, T** target) noexcept
|
||||||
{
|
{
|
||||||
auto it = m_values.find(key);
|
auto it = m_values.find(key);
|
||||||
if (it == m_values.end())
|
if (it == m_values.end())
|
||||||
@@ -107,7 +171,7 @@ namespace minipp
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
EResult SetValue(const std::string& name, const T& value, bool allowOverwrite = false) noexcept
|
EResult SetValue(const std::string& name, std::unique_ptr<T> value, bool allowOverwrite = false) noexcept
|
||||||
{
|
{
|
||||||
if (m_values.find(name) != m_values.end())
|
if (m_values.find(name) != m_values.end())
|
||||||
if (!allowOverwrite)
|
if (!allowOverwrite)
|
||||||
@@ -115,13 +179,13 @@ namespace minipp
|
|||||||
else
|
else
|
||||||
delete m_values[name];
|
delete m_values[name];
|
||||||
|
|
||||||
m_values[name] = new T(value);
|
m_values[name] = value.release();
|
||||||
return EResult::Success;
|
return EResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Section* GetSubSection(const std::string& key, EResult* result = nullptr) const noexcept;
|
EResult GetSubSection(const std::string& key, Section** destination) const noexcept;
|
||||||
Section* SetSubSection(const std::string& name, std::unique_ptr<Section> value, bool allowOverwrite = false, EResult* result = nullptr) noexcept;
|
EResult SetSubSection(const std::string& name, std::unique_ptr<Section> value, bool allowOverwrite = false) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -130,12 +194,767 @@ namespace minipp
|
|||||||
private:
|
private:
|
||||||
Section m_rootSection{};
|
Section m_rootSection{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::unique_ptr<Value> ParseValue(std::string value);
|
||||||
|
static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs) noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EResult Parse(const std::string& path) noexcept;
|
EResult Parse(const std::string& path) noexcept;
|
||||||
void Write(const std::string& path) noexcept;
|
void Write(const std::string& path) const noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const Section& GetRoot() const noexcept { return m_rootSection; }
|
const Section& GetRoot() const noexcept { return m_rootSection; }
|
||||||
Section& GetRoot() noexcept { return m_rootSection; }
|
Section& GetRoot() noexcept { return m_rootSection; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool IsResultOk(EResult result) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Tools
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool StringStartsWith(const std::string& str, const std::string& beg);
|
||||||
|
static bool StringEndsWith(const std::string& str, const std::string& end);
|
||||||
|
static void StringTrim(std::string& str);
|
||||||
|
static bool IsNameValid(const std::string& name) noexcept;
|
||||||
|
static int64_t FirstIndexOf(const std::string& str, char c) noexcept;
|
||||||
|
static int64_t LastIndexOf(const std::string& str, char c) noexcept;
|
||||||
|
static std::pair<std::string, std::string> SplitInTwo(const std::string& str, int64_t firstLength) noexcept;
|
||||||
|
static std::vector<std::string> SplitByDelimiter(const std::string& str, char delimiter) noexcept;
|
||||||
|
static void RemoveAll(std::string& str, char old);
|
||||||
|
static bool IsIntegerDecimal(const std::string& str) noexcept;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MINIPP_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
#if MINIPP_ENABLE_DEBUG_OUTPUT
|
||||||
|
#include <iostream>
|
||||||
|
#define COUT(msg) std::cout << "[minipp] " << msg << std::endl
|
||||||
|
#else
|
||||||
|
#define COUT(msg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ASSERT(condition) if (!condition) abort();
|
||||||
|
|
||||||
|
#define COUT_SYNTAX_ERROR(line, msg) COUT("Error in line " << line << ": " << msg)
|
||||||
|
|
||||||
|
#pragma region Value Types
|
||||||
|
|
||||||
|
minipp::MiniPPFile::Values::StringValue::StringValue(const std::string& str) : m_value(str) {}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::StringValue::Parse(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
m_value = "";
|
||||||
|
for (size_t i = 0; i < str.size(); ++i)
|
||||||
|
{
|
||||||
|
if (str[i] == '\\')
|
||||||
|
{
|
||||||
|
if (i + 1 >= str.size())
|
||||||
|
return EResult::FormatError;
|
||||||
|
|
||||||
|
switch (str[i + 1])
|
||||||
|
{
|
||||||
|
case '\"':
|
||||||
|
m_value.push_back('\"');
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
m_value.push_back('\n');
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
m_value.push_back('\t');
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
m_value.push_back('\r');
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
m_value.push_back('\\');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else if (str[i] == '"')
|
||||||
|
return EResult::FormatError;
|
||||||
|
else
|
||||||
|
m_value.push_back(str[i]);
|
||||||
|
}
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::StringValue::ToString(std::string& destination) const noexcept
|
||||||
|
{
|
||||||
|
std::string sanitizedValue = m_value;
|
||||||
|
for (size_t i = 0; i < sanitizedValue.size(); ++i)
|
||||||
|
{
|
||||||
|
switch (sanitizedValue[i])
|
||||||
|
{
|
||||||
|
case '\n':
|
||||||
|
sanitizedValue.replace(i, 1, "\\n");
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
sanitizedValue.replace(i, 1, "\\t");
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
sanitizedValue.replace(i, 1, "\\r");
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
sanitizedValue.insert(i, 1, '\\');
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
sanitizedValue.insert(i, 1, '\\');
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination = "\"" + sanitizedValue + "\"";
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& minipp::MiniPPFile::Values::StringValue::GetValue() const noexcept
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::MiniPPFile::Values::IntValue::IntValue(int64_t value) : m_value(value) {}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
std::string sanitizedValue = str;
|
||||||
|
Tools::RemoveAll(sanitizedValue, '_');
|
||||||
|
if (sanitizedValue.empty())
|
||||||
|
return EResult::FormatError;
|
||||||
|
|
||||||
|
char lastCharacter = str.back();
|
||||||
|
auto rest = str.substr(0, str.size() - 1);
|
||||||
|
if (lastCharacter == 'h')
|
||||||
|
{
|
||||||
|
m_value = std::stoll(rest, nullptr, 16);
|
||||||
|
m_style = EIntStyle::Hexadecimal;
|
||||||
|
}
|
||||||
|
else if (lastCharacter == 'b')
|
||||||
|
{
|
||||||
|
m_value = std::stoll(rest, nullptr, 2);
|
||||||
|
m_style = EIntStyle::Binary;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!Tools::IsIntegerDecimal(sanitizedValue))
|
||||||
|
return EResult::FormatError;
|
||||||
|
m_value = std::stoll(sanitizedValue);
|
||||||
|
m_style = EIntStyle::Decimal;
|
||||||
|
}
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::IntValue::ToString(std::string& destination) const noexcept
|
||||||
|
{
|
||||||
|
switch (m_style)
|
||||||
|
{
|
||||||
|
case EIntStyle::Decimal:
|
||||||
|
{
|
||||||
|
destination = std::to_string(m_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EIntStyle::Hexadecimal:
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << m_value << "h";
|
||||||
|
destination = ss.str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EIntStyle::Binary:
|
||||||
|
{
|
||||||
|
std::bitset<64> bs(m_value);
|
||||||
|
destination = bs.to_string() + "b";
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < destination.size(); ++i)
|
||||||
|
if (destination[i] == '1')
|
||||||
|
{
|
||||||
|
destination = destination.substr(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == destination.size())
|
||||||
|
destination = "0b";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t minipp::MiniPPFile::Values::IntValue::GetValue() const noexcept
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::MiniPPFile::Values::BooleanValue::BooleanValue(bool value) : m_value(value) {}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::BooleanValue::Parse(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
if (str == "true")
|
||||||
|
m_value = true;
|
||||||
|
else if (str == "false")
|
||||||
|
m_value = false;
|
||||||
|
else
|
||||||
|
return EResult::FormatError;
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::BooleanValue::ToString(std::string& destination) const noexcept
|
||||||
|
{
|
||||||
|
destination = m_value ? "true" : "false";
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool minipp::MiniPPFile::Values::BooleanValue::GetValue() const noexcept
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::MiniPPFile::Values::FloatValue::FloatValue(double value) : m_value(value) {}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::FloatValue::Parse(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::stod(str);
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::FloatValue::ToString(std::string& destination) const noexcept
|
||||||
|
{
|
||||||
|
destination = std::to_string(m_value);
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
double minipp::MiniPPFile::Values::FloatValue::GetValue() const noexcept
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
std::string nValue = str;
|
||||||
|
if (str.front() != '[' || str.back() != ']')
|
||||||
|
return EResult::FormatError;
|
||||||
|
nValue = str.substr(1, str.size() - 2);
|
||||||
|
if (nValue.empty())
|
||||||
|
return EResult::Success;
|
||||||
|
|
||||||
|
int64_t bracketCounter = 0;
|
||||||
|
bool isInString = false;
|
||||||
|
|
||||||
|
std::vector<std::string> elements;
|
||||||
|
std::string currentElement;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < str.size(); ++i)
|
||||||
|
{
|
||||||
|
char c = str[i];
|
||||||
|
|
||||||
|
if (isInString)
|
||||||
|
{
|
||||||
|
if (c == '\\')
|
||||||
|
++i;
|
||||||
|
else if (c == '"')
|
||||||
|
{
|
||||||
|
isInString = false;
|
||||||
|
currentElement += c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
currentElement += c;
|
||||||
|
}
|
||||||
|
else if (c == '\\')
|
||||||
|
++i;
|
||||||
|
else if (c == '"')
|
||||||
|
{
|
||||||
|
currentElement += c;
|
||||||
|
isInString = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (c == '[')
|
||||||
|
{
|
||||||
|
if (++bracketCounter > 1)
|
||||||
|
currentElement += c;
|
||||||
|
}
|
||||||
|
else if (c == ']')
|
||||||
|
{
|
||||||
|
--bracketCounter;
|
||||||
|
if (bracketCounter < 0)
|
||||||
|
return EResult::FormatError;
|
||||||
|
else if (bracketCounter >= 1)
|
||||||
|
currentElement += c;
|
||||||
|
}
|
||||||
|
else if (c == ',' && bracketCounter == 1)
|
||||||
|
{
|
||||||
|
elements.push_back(currentElement);
|
||||||
|
currentElement = "";
|
||||||
|
}
|
||||||
|
else if (c != ' ' && c != '\t')
|
||||||
|
currentElement += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bracketCounter != 0)
|
||||||
|
return EResult::FormatError;
|
||||||
|
|
||||||
|
if (!currentElement.empty())
|
||||||
|
elements.push_back(currentElement);
|
||||||
|
size_t lastTypeIdHash = 0;
|
||||||
|
bool hasTypeHash = false;
|
||||||
|
|
||||||
|
for (auto& elem : elements)
|
||||||
|
{
|
||||||
|
auto parsed = ParseValue(elem);
|
||||||
|
if (parsed == nullptr)
|
||||||
|
return EResult::FormatError;
|
||||||
|
|
||||||
|
if (!hasTypeHash)
|
||||||
|
{
|
||||||
|
lastTypeIdHash = typeid(*parsed).hash_code();
|
||||||
|
hasTypeHash = true;
|
||||||
|
}
|
||||||
|
else if (typeid(*parsed).hash_code() != lastTypeIdHash)
|
||||||
|
return EResult::FormatError;
|
||||||
|
|
||||||
|
m_values.push_back(std::move(parsed));
|
||||||
|
}
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Values::ArrayValue::ToString(std::string& destination) const noexcept
|
||||||
|
{
|
||||||
|
std::string buf;
|
||||||
|
size_t lastTypeIdHash = 0;
|
||||||
|
bool hasTypeHash = false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
for (const auto& val : m_values)
|
||||||
|
{
|
||||||
|
EResult result = val->ToString(buf);
|
||||||
|
if (!IsResultOk(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (!hasTypeHash)
|
||||||
|
{
|
||||||
|
lastTypeIdHash = typeid(*val).hash_code();
|
||||||
|
hasTypeHash = true;
|
||||||
|
}
|
||||||
|
else if (typeid(*val).hash_code() != lastTypeIdHash)
|
||||||
|
return EResult::ArrayDataTypeInconsitency;
|
||||||
|
|
||||||
|
ss << buf << ", ";
|
||||||
|
}
|
||||||
|
std::string valueString = ss.str();
|
||||||
|
if (!valueString.empty())
|
||||||
|
valueString = valueString.substr(0, valueString.size() - 2);
|
||||||
|
destination = "[" + valueString + "]";
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
minipp::MiniPPFile::Section::~Section()
|
||||||
|
{
|
||||||
|
for (auto& pair : m_values)
|
||||||
|
delete pair.second;
|
||||||
|
for (auto& pair : m_subSections)
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Section::GetSubSection(const std::string& key, Section** destination) const noexcept
|
||||||
|
{
|
||||||
|
auto pathIndex = key.find('.');
|
||||||
|
std::string thisKey = key;
|
||||||
|
std::string rest;
|
||||||
|
if (pathIndex != std::string::npos)
|
||||||
|
{
|
||||||
|
thisKey = key.substr(0, pathIndex);
|
||||||
|
rest = key.substr(pathIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = m_subSections.find(thisKey);
|
||||||
|
if (it == m_subSections.end())
|
||||||
|
{
|
||||||
|
COUT("Sub-Section not found: " << thisKey);
|
||||||
|
return EResult::SectionNotPresent;
|
||||||
|
}
|
||||||
|
if (rest.empty())
|
||||||
|
{
|
||||||
|
*destination = it->second;
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second->GetSubSection(rest, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Section::SetSubSection(const std::string& name, std::unique_ptr<Section> value, bool allowOverwrite) noexcept
|
||||||
|
{
|
||||||
|
if (m_subSections.find(name) != m_subSections.end())
|
||||||
|
if (!allowOverwrite)
|
||||||
|
return EResult::SectionAlreadyPresent;
|
||||||
|
else
|
||||||
|
delete m_subSections[name];
|
||||||
|
|
||||||
|
m_subSections[name] = value.release();
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<minipp::MiniPPFile::Value> minipp::MiniPPFile::ParseValue(std::string value)
|
||||||
|
{
|
||||||
|
char valueFirstChar = value.front();
|
||||||
|
char valueLastChar = value.back();
|
||||||
|
if (valueFirstChar == '"')
|
||||||
|
{
|
||||||
|
if (valueLastChar != '"')
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// is string
|
||||||
|
value = value.substr(1, value.size() - 2);
|
||||||
|
auto strValue = std::make_unique<Values::StringValue>();
|
||||||
|
auto parseResult = strValue->Parse(value);
|
||||||
|
if (!IsResultOk(parseResult))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return strValue;
|
||||||
|
}
|
||||||
|
else if (valueLastChar == 'e')
|
||||||
|
{
|
||||||
|
auto boolValue = std::make_unique<Values::BooleanValue>();
|
||||||
|
auto parseResult = boolValue->Parse(value);
|
||||||
|
if (!IsResultOk(parseResult))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return boolValue;
|
||||||
|
}
|
||||||
|
else if (valueLastChar == 'f')
|
||||||
|
{
|
||||||
|
auto floatValue = std::make_unique<Values::FloatValue>();
|
||||||
|
auto parseResult = floatValue->Parse(value);
|
||||||
|
if (!IsResultOk(parseResult))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return floatValue;
|
||||||
|
}
|
||||||
|
else if (valueLastChar == ']')
|
||||||
|
{
|
||||||
|
auto arrayValue = std::make_unique<Values::ArrayValue>();
|
||||||
|
auto parseResult = arrayValue->Parse(value);
|
||||||
|
if (!IsResultOk(parseResult))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return arrayValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto intValue = std::make_unique<Values::IntValue>();
|
||||||
|
auto parseResult = intValue->Parse(value);
|
||||||
|
if (!IsResultOk(parseResult))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::ofstream& ofs) noexcept
|
||||||
|
{
|
||||||
|
if (section->m_values.size() > 0)
|
||||||
|
{
|
||||||
|
for (const auto& pair : section->m_values)
|
||||||
|
{
|
||||||
|
if (!Tools::IsNameValid(pair.first))
|
||||||
|
{
|
||||||
|
COUT("Invalid name for key: " << pair.first);
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& comment : pair.second->m_comments)
|
||||||
|
ofs << comment << std::endl;
|
||||||
|
|
||||||
|
ofs << pair.first << " = ";
|
||||||
|
std::string valueString;
|
||||||
|
auto result = pair.second->ToString(valueString);
|
||||||
|
if (!IsResultOk(result))
|
||||||
|
return result;
|
||||||
|
ofs << valueString << std::endl;
|
||||||
|
}
|
||||||
|
ofs << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pair : section->m_subSections)
|
||||||
|
{
|
||||||
|
if (!Tools::IsNameValid(pair.first))
|
||||||
|
{
|
||||||
|
COUT("Invalid name for section: " << pair.first);
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& comment : pair.second->m_comments)
|
||||||
|
ofs << comment << std::endl;
|
||||||
|
|
||||||
|
ofs << "[" << pair.first << "]" << std::endl;
|
||||||
|
auto result = WriteSection(pair.second, ofs);
|
||||||
|
if (!IsResultOk(result))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept
|
||||||
|
{
|
||||||
|
std::ifstream ifs;
|
||||||
|
ifs.open(path);
|
||||||
|
if (!ifs.is_open())
|
||||||
|
return EResult::FileIOError;
|
||||||
|
|
||||||
|
int64_t lineCounter = 0;
|
||||||
|
Section* currentSection = nullptr;
|
||||||
|
|
||||||
|
std::vector<std::string> commentBuffer;
|
||||||
|
|
||||||
|
std::string currentLine;
|
||||||
|
while (std::getline(ifs, currentLine))
|
||||||
|
{
|
||||||
|
++lineCounter;
|
||||||
|
Tools::StringTrim(currentLine);
|
||||||
|
if (currentLine.empty())
|
||||||
|
continue;
|
||||||
|
char firstChar = currentLine[0];
|
||||||
|
char lastChar = currentLine[currentLine.size() - 1];
|
||||||
|
|
||||||
|
if (firstChar == '#')
|
||||||
|
{
|
||||||
|
commentBuffer.push_back(currentLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChar == '[')
|
||||||
|
{
|
||||||
|
if (lastChar != ']')
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
std::string sectionName = currentLine.substr(1, currentLine.size() - 2);
|
||||||
|
Tools::StringTrim(sectionName);
|
||||||
|
if (sectionName.empty())
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
// Create section tree
|
||||||
|
Section* ubSection = &m_rootSection;
|
||||||
|
|
||||||
|
std::vector<std::string> sectionPath = Tools::SplitByDelimiter(sectionName, '.');
|
||||||
|
for (size_t i = 0; i < sectionPath.size(); ++i)
|
||||||
|
{
|
||||||
|
const std::string& sectionName = sectionPath[i];
|
||||||
|
if (!Tools::IsNameValid(sectionName))
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
EResult result;
|
||||||
|
result = ubSection->SetSubSection(sectionName, std::make_unique<Section>(), false);
|
||||||
|
if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1)
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
result = ubSection->GetSubSection(sectionName, &ubSection);
|
||||||
|
ASSERT(IsResultOk(result)); // should never happen
|
||||||
|
}
|
||||||
|
currentSection = ubSection;
|
||||||
|
currentSection->m_comments = commentBuffer;
|
||||||
|
commentBuffer.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (currentSection == nullptr)
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '=');
|
||||||
|
if (keyValueDelimiterIndex == -1)
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex);
|
||||||
|
Tools::StringTrim(keyValuePair.first);
|
||||||
|
Tools::StringTrim(keyValuePair.second);
|
||||||
|
if (keyValuePair.first.empty())
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Expected key in line.");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
if (keyValuePair.second.empty())
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
auto parsedValue = ParseValue(keyValuePair.second);
|
||||||
|
if (parsedValue == nullptr)
|
||||||
|
{
|
||||||
|
COUT_SYNTAX_ERROR(lineCounter, "Invalid value");
|
||||||
|
return EResult::FormatError;
|
||||||
|
}
|
||||||
|
parsedValue->m_comments = commentBuffer;
|
||||||
|
commentBuffer.clear();
|
||||||
|
|
||||||
|
currentSection->SetValue(keyValuePair.first, std::move(parsedValue), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void minipp::MiniPPFile::Write(const std::string& path) const noexcept
|
||||||
|
{
|
||||||
|
std::ofstream ofs;
|
||||||
|
ofs.open(path);
|
||||||
|
if (!ofs.is_open())
|
||||||
|
return;
|
||||||
|
|
||||||
|
WriteSection(&m_rootSection, ofs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool minipp::MiniPPFile::IsResultOk(EResult result) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<int32_t>(result) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma region Tools
|
||||||
|
bool minipp::MiniPPFile::Tools::StringStartsWith(const std::string& str, const std::string& beg)
|
||||||
|
{
|
||||||
|
if (str.size() < beg.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < beg.size(); ++i)
|
||||||
|
if (str[i] != beg[i])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool minipp::MiniPPFile::Tools::StringEndsWith(const std::string& str, const std::string& end)
|
||||||
|
{
|
||||||
|
if (str.size() < end.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < end.size(); ++i)
|
||||||
|
if (str[str.size() - i - 1] != end[end.size() - i - 1])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void minipp::MiniPPFile::Tools::StringTrim(std::string& str)
|
||||||
|
{
|
||||||
|
if (str.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = str.size() - 1;
|
||||||
|
while (str[start] == ' ' || str[start] == '\t')
|
||||||
|
start++;
|
||||||
|
while (str[end] == ' ' || str[end] == '\t')
|
||||||
|
end--;
|
||||||
|
|
||||||
|
str = str.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool minipp::MiniPPFile::Tools::IsNameValid(const std::string& name) noexcept
|
||||||
|
{
|
||||||
|
for (const char c : name)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(c >= 'a' && c <= 'z') ||
|
||||||
|
(c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= '0' && c <= '9') ||
|
||||||
|
(c == '_'))
|
||||||
|
continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t minipp::MiniPPFile::Tools::FirstIndexOf(const std::string& str, char c) noexcept
|
||||||
|
{
|
||||||
|
int64_t index = 0;
|
||||||
|
for (const char ch : str)
|
||||||
|
{
|
||||||
|
if (ch == c)
|
||||||
|
return index;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t minipp::MiniPPFile::Tools::LastIndexOf(const std::string& str, char c) noexcept
|
||||||
|
{
|
||||||
|
int64_t index = str.size() - 1;
|
||||||
|
for (int64_t i = str.size() - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
if (str[i] == c)
|
||||||
|
return index;
|
||||||
|
--index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, std::string> minipp::MiniPPFile::Tools::SplitInTwo(const std::string& str, int64_t firstLength) noexcept
|
||||||
|
{
|
||||||
|
return std::make_pair(str.substr(0, firstLength), str.substr(firstLength + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept
|
||||||
|
{
|
||||||
|
std::vector<std::string> elements;
|
||||||
|
std::string tmp;
|
||||||
|
|
||||||
|
for (const char c : str)
|
||||||
|
{
|
||||||
|
if (c == delimiter)
|
||||||
|
{
|
||||||
|
elements.push_back(tmp);
|
||||||
|
tmp.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tmp.push_back(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tmp.empty())
|
||||||
|
elements.push_back(tmp);
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
void minipp::MiniPPFile::Tools::RemoveAll(std::string& str, char old)
|
||||||
|
{
|
||||||
|
str.erase(std::remove(str.begin(), str.end(), old), str.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool minipp::MiniPPFile::Tools::IsIntegerDecimal(const std::string& str) noexcept
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < str.size(); ++i)
|
||||||
|
if (str[i] < '0' || str[i] > '9')
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
#endif
|
||||||
@@ -1,22 +1,45 @@
|
|||||||
|
#define MINIPP_IMPLEMENTATION
|
||||||
#include "minipp/minipp.hpp"
|
#include "minipp/minipp.hpp"
|
||||||
|
|
||||||
|
using namespace minipp;
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
minipp::MiniPPFile file;
|
EResult result;
|
||||||
auto result = file.Parse("test.mini");
|
|
||||||
|
|
||||||
auto& section = file.GetRoot();
|
MiniPPFile file;
|
||||||
|
result = file.Parse("test.mini");
|
||||||
minipp::MiniPPFile::Section* profileSection = section.GetSubSection("profile", &result);
|
|
||||||
|
|
||||||
minipp::MiniPPFile::IntValue* value;
|
auto& root = file.GetRoot();
|
||||||
auto result1 = profileSection->GetValue(&value, "window_width");
|
|
||||||
|
|
||||||
auto result2 = value->GetValue();
|
MiniPPFile::Section* gameSection = nullptr;
|
||||||
|
result = root.GetSubSection("game", &gameSection);
|
||||||
|
MiniPPFile::Values::StringValue* nameValue = nullptr;
|
||||||
|
result = gameSection->GetValue("name", &nameValue);
|
||||||
|
MiniPPFile::Values::IntValue* yearValue = nullptr;
|
||||||
|
result = gameSection->GetValue("year", &yearValue);
|
||||||
|
MiniPPFile::Values::FloatValue* completionPercentage = nullptr;
|
||||||
|
result = gameSection->GetValue("completionPercentage", &completionPercentage);
|
||||||
|
MiniPPFile::Values::BooleanValue* isCompleted = nullptr;
|
||||||
|
result = gameSection->GetValue("is_completed", &isCompleted);
|
||||||
|
|
||||||
auto marianProfile = section.GetSubSection("profile.marian", &result);
|
MiniPPFile::Section* windowSection = nullptr;
|
||||||
minipp::MiniPPFile::StringValue* dockOutlinerType;
|
result = gameSection->GetSubSection("window", &windowSection);
|
||||||
marianProfile->GetValue(&dockOutlinerType, "dock_outliner");
|
MiniPPFile::Values::ArrayValue* dimensionsValue = nullptr;
|
||||||
|
result = windowSection->GetValue("dimensions", &dimensionsValue);
|
||||||
|
MiniPPFile::Values::IntValue* closeFlags = nullptr;
|
||||||
|
result = windowSection->GetValue("close_flags", &closeFlags);
|
||||||
|
MiniPPFile::Values::StringValue* hexTest = nullptr;
|
||||||
|
result = windowSection->GetValue("hex_test", &hexTest);
|
||||||
|
|
||||||
|
MiniPPFile::Section* windowPlatformSection = nullptr;
|
||||||
|
result = gameSection->GetSubSection("window.platform", &windowPlatformSection);
|
||||||
|
MiniPPFile::Values::ArrayValue* targetsValue = nullptr;
|
||||||
|
result = windowPlatformSection->GetValue("targets", &targetsValue);
|
||||||
|
MiniPPFile::Values::ArrayValue* pointsValue = nullptr;
|
||||||
|
result = windowPlatformSection->GetValue("points", &pointsValue);
|
||||||
|
|
||||||
|
file.Write("test_out.mini");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
[profile]
|
[game]
|
||||||
dock_outliner="left"
|
name = "Test Game"
|
||||||
window_width=1000000b
|
version = "1.0.0"
|
||||||
window_height=fh
|
year = 2025
|
||||||
|
completionPercentage = 50.0f
|
||||||
|
# Should only be true if completionPercentage is 100
|
||||||
|
is_completed = false
|
||||||
|
|
||||||
[profile.marian]
|
# This section is about
|
||||||
dock_outliner="ri\\ght\\"
|
# the settings of a game window
|
||||||
|
[game.window]
|
||||||
|
dimensions = [1280, 720]
|
||||||
|
close_flags = 101011b
|
||||||
|
hex_test = DEADBEEFh
|
||||||
|
|
||||||
[profile.toyb]
|
[game.window.platform]
|
||||||
dock_outliner="top"
|
targets = ["win32", "macOS", "linux"]
|
||||||
|
points = [[0, 0], [1, 0], [1, 1], [0, 1]]
|
||||||
19
minipp/test_out.mini
Normal file
19
minipp/test_out.mini
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[game]
|
||||||
|
name = "Test Game"
|
||||||
|
completionPercentage = 50.000000
|
||||||
|
version = "1.0.0"
|
||||||
|
year = 2025
|
||||||
|
# Should only be true if completionPercentage is 100
|
||||||
|
is_completed = false
|
||||||
|
|
||||||
|
# This section is about
|
||||||
|
# the settings of a game window
|
||||||
|
[window]
|
||||||
|
dimensions = [1280, 720]
|
||||||
|
close_flags = 101011b
|
||||||
|
hex_test = deadbeefh
|
||||||
|
|
||||||
|
[platform]
|
||||||
|
targets = ["win32", "macOS", "linux"]
|
||||||
|
points = [[0, 0], [1, 0], [1, 1], [0, 1]]
|
||||||
|
|
||||||
Reference in New Issue
Block a user