diff --git a/minipp/DoubleFileTest.cpp b/minipp/DoubleFileTest.cpp
new file mode 100644
index 0000000..4839d92
--- /dev/null
+++ b/minipp/DoubleFileTest.cpp
@@ -0,0 +1,2 @@
+
+#include "minipp/minipp.hpp"
\ No newline at end of file
diff --git a/minipp/minipp.vcxproj b/minipp/minipp.vcxproj
index 88359dd..8c081ca 100644
--- a/minipp/minipp.vcxproj
+++ b/minipp/minipp.vcxproj
@@ -130,8 +130,8 @@
+
-
diff --git a/minipp/minipp.vcxproj.filters b/minipp/minipp.vcxproj.filters
index aee9209..32c0bd4 100644
--- a/minipp/minipp.vcxproj.filters
+++ b/minipp/minipp.vcxproj.filters
@@ -20,10 +20,10 @@
-
+
Quelldateien
-
+
Quelldateien
diff --git a/minipp/minipp/minipp.cpp b/minipp/minipp/minipp.cpp
deleted file mode 100644
index f4ef2d0..0000000
--- a/minipp/minipp/minipp.cpp
+++ /dev/null
@@ -1,436 +0,0 @@
-#include "minipp.hpp"
-#include
-
-#define MINIPP_ENABLE_DEBUG_OUTPUT true
-
-#if MINIPP_ENABLE_DEBUG_OUTPUT
-#include
-#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 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 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(), 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 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 minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept
-{
- std::vector 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(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;
-}
diff --git a/minipp/minipp/minipp.hpp b/minipp/minipp/minipp.hpp
index b5b08c9..44a1a39 100644
--- a/minipp/minipp/minipp.hpp
+++ b/minipp/minipp/minipp.hpp
@@ -6,83 +6,147 @@
#include
#include
+#define MINIPP_ENABLE_DEBUG_OUTPUT true
+
+// define MINIPP_IMPLEMENTATION in one single translation unit before including this header!
+
namespace minipp
{
enum class EResult
{
/* Errors */
- KeyNotPresent = -1,
- KeyAlreadyPresent = -2,
- SectionNotPresent = -3,
- SectionAlreadyPresent = -4,
- FileIOError = -5,
- InvalidDataType = -6,
- FormatError = -7,
+ KeyNotPresent = -1,
+ KeyAlreadyPresent = -2,
+ SectionNotPresent = -3,
+ SectionAlreadyPresent = -4,
+ FileIOError = -5,
+ InvalidDataType = -6,
+ FormatError = -7,
+ ArrayDataTypeInconsitency = -8,
/* OK Codes*/
- Success = 1,
- ValueOverwritten,
- ValueOverwrittenAndDataTypeChanged
+ Success = +1,
+ ValueOverwritten = +2
};
+ enum class EIntStyle
+ {
+ Decimal,
+ Hexadecimal,
+ Binary
+ };
+
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 SplitInTwo(const std::string& str, int64_t firstLength) noexcept;
- static std::vector 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:
class Value
{
+ friend class MiniPPFile;
+
+ protected:
+ std::vector m_comments;
+
public:
virtual EResult Parse(const std::string& str) noexcept = 0;
virtual EResult ToString(std::string& destination) const noexcept = 0;
+ virtual ~Value() = default;
+ std::vector& GetComments() noexcept { return m_comments; }
+ const std::vector& GetComments() const noexcept { return m_comments; }
};
- class StringValue : public Value
+ class Values
{
- private:
- std::string m_value;
-
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 StringValue : public Value
+ {
+ private:
+ std::string m_value;
+
+ 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> 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>& GetValues() noexcept { return m_values; }
+ const std::vector>& 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
{
+ friend class MiniPPFile;
+
private:
std::unordered_map m_values;
std::unordered_map m_subSections;
+ std::vector m_comments;
+
+ public:
+ std::vector& GetComments() noexcept { return m_comments; }
+ const std::vector& GetComments() const noexcept { return m_comments; }
public:
Section() = default;
@@ -92,7 +156,7 @@ namespace minipp
public:
template
- EResult GetValue(T** target, const std::string& key) noexcept
+ EResult GetValue(const std::string& key, T** target) noexcept
{
auto it = m_values.find(key);
if (it == m_values.end())
@@ -107,7 +171,7 @@ namespace minipp
}
template
- EResult SetValue(const std::string& name, const T& value, bool allowOverwrite = false) noexcept
+ EResult SetValue(const std::string& name, std::unique_ptr value, bool allowOverwrite = false) noexcept
{
if (m_values.find(name) != m_values.end())
if (!allowOverwrite)
@@ -115,13 +179,13 @@ namespace minipp
else
delete m_values[name];
- m_values[name] = new T(value);
+ m_values[name] = value.release();
return EResult::Success;
}
public:
- Section* GetSubSection(const std::string& key, EResult* result = nullptr) const noexcept;
- Section* SetSubSection(const std::string& name, std::unique_ptr value, bool allowOverwrite = false, EResult* result = nullptr) noexcept;
+ EResult GetSubSection(const std::string& key, Section** destination) const noexcept;
+ EResult SetSubSection(const std::string& name, std::unique_ptr value, bool allowOverwrite = false) noexcept;
};
public:
@@ -130,12 +194,767 @@ namespace minipp
private:
Section m_rootSection{};
+ private:
+ static std::unique_ptr ParseValue(std::string value);
+ static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs) noexcept;
+
public:
EResult Parse(const std::string& path) noexcept;
- void Write(const std::string& path) noexcept;
+ void Write(const std::string& path) const noexcept;
public:
const Section& GetRoot() const 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 SplitInTwo(const std::string& str, int64_t firstLength) noexcept;
+ static std::vector SplitByDelimiter(const std::string& str, char delimiter) noexcept;
+ static void RemoveAll(std::string& str, char old);
+ static bool IsIntegerDecimal(const std::string& str) noexcept;
+ };
};
-}
\ No newline at end of file
+}
+
+#ifdef MINIPP_IMPLEMENTATION
+
+#include
+#include
+#include
+#include
+#include
+
+#if MINIPP_ENABLE_DEBUG_OUTPUT
+#include
+#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 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 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::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();
+ auto parseResult = strValue->Parse(value);
+ if (!IsResultOk(parseResult))
+ return nullptr;
+
+ return strValue;
+ }
+ else if (valueLastChar == 'e')
+ {
+ auto boolValue = std::make_unique();
+ auto parseResult = boolValue->Parse(value);
+ if (!IsResultOk(parseResult))
+ return nullptr;
+
+ return boolValue;
+ }
+ else if (valueLastChar == 'f')
+ {
+ auto floatValue = std::make_unique();
+ auto parseResult = floatValue->Parse(value);
+ if (!IsResultOk(parseResult))
+ return nullptr;
+
+ return floatValue;
+ }
+ else if (valueLastChar == ']')
+ {
+ auto arrayValue = std::make_unique();
+ auto parseResult = arrayValue->Parse(value);
+ if (!IsResultOk(parseResult))
+ return nullptr;
+
+ return arrayValue;
+ }
+ else
+ {
+ auto intValue = std::make_unique();
+ 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 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 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(), 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(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 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 minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept
+{
+ std::vector 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
\ No newline at end of file
diff --git a/minipp/test.cpp b/minipp/test.cpp
index 7fbda9c..c19a471 100644
--- a/minipp/test.cpp
+++ b/minipp/test.cpp
@@ -1,22 +1,45 @@
+#define MINIPP_IMPLEMENTATION
#include "minipp/minipp.hpp"
+using namespace minipp;
+
int main()
{
- minipp::MiniPPFile file;
- auto result = file.Parse("test.mini");
+ EResult result;
- auto& section = file.GetRoot();
-
- minipp::MiniPPFile::Section* profileSection = section.GetSubSection("profile", &result);
+ MiniPPFile file;
+ result = file.Parse("test.mini");
- minipp::MiniPPFile::IntValue* value;
- auto result1 = profileSection->GetValue(&value, "window_width");
+ auto& root = file.GetRoot();
- 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);
- minipp::MiniPPFile::StringValue* dockOutlinerType;
- marianProfile->GetValue(&dockOutlinerType, "dock_outliner");
+ MiniPPFile::Section* windowSection = nullptr;
+ result = gameSection->GetSubSection("window", &windowSection);
+ 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;
}
\ No newline at end of file
diff --git a/minipp/test.mini b/minipp/test.mini
index f03c0d5..7c63239 100644
--- a/minipp/test.mini
+++ b/minipp/test.mini
@@ -1,10 +1,18 @@
-[profile]
-dock_outliner="left"
-window_width=1000000b
-window_height=fh
+[game]
+name = "Test Game"
+version = "1.0.0"
+year = 2025
+completionPercentage = 50.0f
+# Should only be true if completionPercentage is 100
+is_completed = false
-[profile.marian]
-dock_outliner="ri\\ght\\"
+# This section is about
+# the settings of a game window
+[game.window]
+dimensions = [1280, 720]
+close_flags = 101011b
+hex_test = DEADBEEFh
-[profile.toyb]
-dock_outliner="top"
\ No newline at end of file
+[game.window.platform]
+targets = ["win32", "macOS", "linux"]
+points = [[0, 0], [1, 0], [1, 1], [0, 1]]
\ No newline at end of file
diff --git a/minipp/test_out.mini b/minipp/test_out.mini
new file mode 100644
index 0000000..8e63016
--- /dev/null
+++ b/minipp/test_out.mini
@@ -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]]
+