26 Commits
1.0 ... master

Author SHA1 Message Date
136789d68d Merge branch 'master' of https://github.com/mariiaan/minipp 2025-11-19 01:23:45 +01:00
2c2c567616 Allow nested section names in SetSubSection 2025-11-19 01:23:40 +01:00
8d9d29a684 Fix comments not being serialized correctly 2025-11-14 00:57:55 +01:00
302b2b9a94 Add const array accessor 2025-02-15 01:09:15 +01:00
c304b50eb3 Fix float serialization 2025-02-11 20:03:13 +01:00
c6aa27f47b Add fstream compability 2025-02-10 02:05:06 +01:00
ceb392a198 Improve type safety in GetValueOrDefault 2025-02-07 22:27:57 +01:00
1fd3f05ebd Improve error logging 2025-02-06 05:04:38 +01:00
a518dcac54 Fixed issue where ParseValue does not return the result code 2025-02-06 04:51:20 +01:00
f465f2eb0f Improve error codes 2025-02-06 04:42:55 +01:00
87f39d93f0 Remove unnecessary nValue 2025-02-02 23:10:16 +01:00
b8f88e7586 Move ParseValue to Value base class 2025-02-02 22:55:31 +01:00
08f4df7b04 Improve error handling 2025-02-02 22:53:00 +01:00
4e54e828eb Merge branch 'master' of https://github.com/mariiaan/minipp 2025-02-02 21:47:25 +01:00
2880cfd2f3 Fix bug where escape sequences in string arrays are not parsed correctly 2025-02-02 21:47:21 +01:00
538c977f14 Update README.md 2025-02-02 20:55:11 +01:00
d7bc969b70 Update README.md 2025-02-02 20:54:38 +01:00
04dcc77db6 Update README.md 2025-02-02 20:51:45 +01:00
3f96d049e5 Update README.md 2025-02-02 05:40:57 +01:00
e3c3b76caa Add easy API 2025-02-02 05:39:08 +01:00
4ee8c32822 Change macro name + remove unnecessary assert 2025-02-02 04:39:50 +01:00
e13cc5a94e Add support for getting all sections 2025-02-01 23:29:56 +01:00
ef0f4ba908 Merge branch 'master' of https://github.com/mariiaan/minipp 2025-02-01 21:09:54 +01:00
43db485cec Fix newlines 2025-02-01 21:09:48 +01:00
46e2129787 Update README.md 2025-02-01 20:46:09 +01:00
27b720245c Fix links 2025-02-01 20:42:56 +01:00
6 changed files with 930 additions and 755 deletions

View File

@@ -1,6 +1,6 @@
# minipp # minipp
A lightweight, header-only C++14 parser and writer for the mini config format. A lightweight, header-only C++14 parser and writer for the [mini config format](https://github.com/ToyB-Chan/mini-file-format).
## Description ## Description
minipp provides a convenient way to parse and write configuration files in the "mini" format. This format is characterized by its human-readable structure and nested sections, making it ideal for application configurations. minipp provides a convenient way to parse and write configuration files in the "mini" format. This format is characterized by its human-readable structure and nested sections, making it ideal for application configurations.
@@ -15,17 +15,6 @@ minipp provides a convenient way to parse and write configuration files in the "
- **Single Header**: minipp is a single-header library, meaning you only need to include one file. - **Single Header**: minipp is a single-header library, meaning you only need to include one file.
## Usage
To use minipp in your project:
```cpp
#define MINIPP_IMPLEMENTATION
#include "minipp.hpp"
```
Then, you can use `minipp::MiniPPFile` to parse and write mini files throughout your code.
### Example Usage ### Example Usage
```cpp ```cpp
@@ -45,12 +34,16 @@ int main()
MiniPPFile::Section* gameSection = nullptr; MiniPPFile::Section* gameSection = nullptr;
result = root.GetSubSection("game", &gameSection); result = root.GetSubSection("game", &gameSection);
// "Easy" API
int64_t test = gameSection.GetValueOrDefault<MiniPPFile::Values::IntValue>("year", 1999);
// Verbose API
MiniPPFile::Values::StringValue* nameValue = nullptr; MiniPPFile::Values::StringValue* nameValue = nullptr;
result = gameSection->GetValue("name", &nameValue); result = gameSection->GetValue("name", &nameValue);
MiniPPFile::Values::IntValue* yearValue = nullptr; MiniPPFile::Values::IntValue* yearValue = nullptr;
result = gameSection->GetValue("year", &yearValue); result = gameSection->GetValue("year", &yearValue);
// Get a sub-section (section of a section (stated in the MINI file with "[game.window]") // Get a sub-section (section of a section, stated in the MINI file with "[game.window]")
MiniPPFile::Section* windowSection = nullptr; MiniPPFile::Section* windowSection = nullptr;
result = gameSection->GetSubSection("window", &windowSection); result = gameSection->GetSubSection("window", &windowSection);
@@ -58,12 +51,12 @@ int main()
// Get a sub-section by using the dot operator // Get a sub-section by using the dot operator
result = gameSection->GetSubSection("window.platform", &windowPlatformSection); result = gameSection->GetSubSection("window.platform", &windowPlatformSection);
MiniPPFile::Values::ArrayValue* pointsValue = nullptr; MiniPPFile::Values::ArrayValue* platformTargetsValue = nullptr;
// Retrieve an array by using the relative value path (the game section is a child of the root section) // Retrieve an array by using the relative value path (the game section is a child of the root section)
result = root.GetValue("game.window.platform.targets", &pointsValue); result = root.GetValue("game.window.platform.targets", &platformTargetsValue);
// Modify the "targets" array by adding a new value // Modify the "targets" array by adding a new value
pointsValue->GetValues().push_back(std::make_unique<MiniPPFile::Values::StringValue>("haiku")); platformTargetsValue->GetValue().push_back(std::make_unique<MiniPPFile::Values::StringValue>("haiku"));
// Serialize the config // Serialize the config
result = file.Write("test_out.mini"); result = file.Write("test_out.mini");
@@ -100,8 +93,11 @@ int main()
MiniPPFile::Values::StringValue* nameValue = nullptr; MiniPPFile::Values::StringValue* nameValue = nullptr;
result = gameSection->GetValue("name", &nameValue); result = gameSection->GetValue("name", &nameValue);
// Navigate nested sections // .. OR ..
std::string nameValue = gameSection->GetValueOrDefault<MiniPPFile::Values::StringValue>("name", "Unknown");
// Navigate nested sections
MiniPPFile::Section* windowSection = nullptr; MiniPPFile::Section* windowSection = nullptr;
result = gameSection->GetSubSection("window", &windowSection); result = gameSection->GetSubSection("window", &windowSection);
@@ -121,7 +117,7 @@ int main()
result = root.GetValue("game.window.platform.points", &pointsValue); result = root.GetValue("game.window.platform.points", &pointsValue);
// Modify the array values (or read them) // Modify the array values (or read them)
pointsValue->GetValues().push_back(std::make_unique<MiniPPFile::Values::StringValue>("haiku")); pointsValue->GetValue().push_back(std::make_unique<MiniPPFile::Values::StringValue>("haiku"));
``` ```
5. **Write new files**: 5. **Write new files**:
@@ -132,11 +128,11 @@ int main()
## Example ## Example
An example mini file is contained in this [repository](test.mini). The full mini file format specilization can be found [here](https://github.com/ToyB-Chan/mini-file-format). An example mini file is contained in this [repository](minipp/test.mini). The full mini file format specification can be found [here](https://github.com/ToyB-Chan/mini-file-format).
## Installation ## Installation
1. Copy the contents of "minipp.hpp" to a new file in your project. 1. Copy the contents of [minipp.hpp](minipp/minipp.hpp) to a new file in your project.
2. #Include "minipp.hpp" after defining MINIPP_IMPLEMENTATION in one single cpp file. 2. #Include "minipp.hpp" after defining MINIPP_IMPLEMENTATION in one single cpp file.
3. #Include "minipp.hpp" in any other desired files without the IMPLEMENTATION define! 3. #Include "minipp.hpp" in any other desired files without the IMPLEMENTATION define!

View File

@@ -41,6 +41,24 @@ namespace minipp
InvalidDataType = -6, InvalidDataType = -6,
FormatError = -7, FormatError = -7,
ArrayDataTypeInconsistency = -8, ArrayDataTypeInconsistency = -8,
BadEscapeSequence = -9,
UnknownEscapeSequence = -10,
UnescapedStringValue = -11,
ValueEmpty = -12,
IntegerValueInvalid = -13,
IntegerValueOutOfRange = -14,
IntegerStyleInvalid = -15,
FloatValueInvalid = -16,
BooleanValueInvalid = -17,
ArrayNotEnclosed = -18,
ArrayBracketsInbalanced = -19,
InvalidName = -20,
SectionExpectedClosingBracket = -21,
EmptySectionName = -22,
KeyValuePairNotInSection = -23,
ExpectedKeyValuePair = -24,
KeyEmpty = -25,
MissingQuote = -26,
/* OK Codes */ /* OK Codes */
Success = +1, Success = +1,
@@ -70,6 +88,9 @@ namespace minipp
virtual ~Value() = default; virtual ~Value() = default;
std::vector<std::string>& GetComments() noexcept { return m_comments; } std::vector<std::string>& GetComments() noexcept { return m_comments; }
const std::vector<std::string>& GetComments() const noexcept { return m_comments; } const std::vector<std::string>& GetComments() const noexcept { return m_comments; }
public:
static std::unique_ptr<Value> ParseValue(std::string value, EResult* result = nullptr);
}; };
class Values class Values
@@ -77,78 +98,101 @@ namespace minipp
public: public:
class StringValue : public Value class StringValue : public Value
{ {
public:
using BaseType = std::string;
private: private:
std::string m_value; BaseType m_value;
public: public:
StringValue() = default; StringValue() = default;
StringValue(const std::string& str) : m_value(str) {}; StringValue(const BaseType& str) : m_value(str) {};
EResult Parse(const std::string& str) noexcept override; EResult Parse(const std::string& str) noexcept override;
EResult ToString(std::string& destination) const noexcept override; EResult ToString(std::string& destination) const noexcept override;
const std::string& GetValue() const noexcept { return m_value; } const BaseType& GetValue() const noexcept { return m_value; }
}; };
class IntValue : public Value class IntValue : public Value
{ {
public:
using BaseType = int64_t;
private: private:
int64_t m_value = 0; BaseType m_value = 0;
EIntStyle m_style = EIntStyle::Decimal; EIntStyle m_style = EIntStyle::Decimal;
public: public:
IntValue() = default; IntValue() = default;
IntValue(int64_t value) : m_value(value) {}; IntValue(BaseType value) : m_value(value) {};
EResult Parse(const std::string& str) noexcept override; EResult Parse(const std::string& str) noexcept override;
EResult ToString(std::string& destination) const noexcept override; EResult ToString(std::string& destination) const noexcept override;
int64_t GetValue() const noexcept { return m_value; } BaseType GetValue() const noexcept { return m_value; }
}; };
class BooleanValue : public Value class BooleanValue : public Value
{ {
public:
using BaseType = bool;
private: private:
bool m_value = false; BaseType m_value = false;
public: public:
BooleanValue() = default; BooleanValue() = default;
BooleanValue(bool value) : m_value(value) {}; BooleanValue(BaseType value) : m_value(value) {};
EResult Parse(const std::string& str) noexcept override; EResult Parse(const std::string& str) noexcept override;
EResult ToString(std::string& destination) const noexcept override; EResult ToString(std::string& destination) const noexcept override;
bool GetValue() const noexcept { return m_value; } BaseType GetValue() const noexcept { return m_value; }
}; };
class FloatValue : public Value class FloatValue : public Value
{ {
public:
using BaseType = double;
private: private:
double m_value = 0.0f; BaseType m_value = 0.0f;
public: public:
FloatValue() = default; FloatValue() = default;
FloatValue(double value) : m_value(value) {}; FloatValue(BaseType value) : m_value(value) {};
EResult Parse(const std::string& str) noexcept override; EResult Parse(const std::string& str) noexcept override;
EResult ToString(std::string& destination) const noexcept override; EResult ToString(std::string& destination) const noexcept override;
double GetValue() const noexcept { return m_value; } BaseType GetValue() const noexcept { return m_value; }
}; };
class ArrayValue : public Value class ArrayValue : public Value
{ {
public:
using BaseType = std::vector<Value*>;
private: private:
std::vector<std::unique_ptr<Value>> m_values; BaseType m_values;
public: public:
ArrayValue() = default; ArrayValue() = default;
ArrayValue(const ArrayValue&) = delete; ArrayValue(const ArrayValue&) = delete;
ArrayValue& operator=(const ArrayValue&) = delete; ArrayValue& operator=(const ArrayValue&) = delete;
virtual ~ArrayValue();
public: public:
EResult Parse(const std::string& str) noexcept override; EResult Parse(const std::string& str) noexcept override;
EResult ToString(std::string& destination) const noexcept override; EResult ToString(std::string& destination) const noexcept override;
std::vector<std::unique_ptr<Value>>& GetValues() noexcept { return m_values; } BaseType& GetValue() noexcept { return m_values; }
const std::vector<std::unique_ptr<Value>>& GetValues() const noexcept { return m_values; } const BaseType& GetValue() const noexcept { return m_values; }
Value* operator[](size_t index) noexcept Value* operator[](size_t index) noexcept
{ {
if (index >= m_values.size()) if (index >= m_values.size())
return nullptr; return nullptr;
return m_values[index].get(); return m_values[index];
}
const Value* operator[](size_t index) const noexcept
{
if (index >= m_values.size())
return nullptr;
return m_values[index];
} }
}; };
}; };
@@ -165,6 +209,10 @@ namespace minipp
public: public:
std::vector<std::string>& GetComments() noexcept { return m_comments; } std::vector<std::string>& GetComments() noexcept { return m_comments; }
const std::vector<std::string>& GetComments() const noexcept { return m_comments; } const std::vector<std::string>& GetComments() const noexcept { return m_comments; }
std::unordered_map<std::string, Value*>& GetValues() noexcept { return m_values; }
const std::unordered_map<std::string, Value*>& GetValues() const noexcept { return m_values; }
std::unordered_map<std::string, Section*>& GetSubSections() noexcept { return m_subSections; }
const std::unordered_map<std::string, Section*>& GetSubSections() const noexcept { return m_subSections; }
public: public:
Section() = default; Section() = default;
@@ -177,16 +225,20 @@ namespace minipp
EResult GetValue(const std::string& key, ValueDataType** target) noexcept EResult GetValue(const std::string& key, ValueDataType** target) noexcept
{ {
static_assert(std::is_base_of<Value, ValueDataType>::value, "ValueDataType must be a subclass of Value"); static_assert(std::is_base_of<Value, ValueDataType>::value, "ValueDataType must be a subclass of Value");
int64_t firstSeparatorIndex = Tools::FirstIndexOf(key, '.'); int64_t firstSeparatorIndex = Tools::FirstIndexOf(key, '.');
if (firstSeparatorIndex != -1) if (firstSeparatorIndex != -1)
{ {
std::string thisKey = key.substr(0, firstSeparatorIndex); std::string thisKey = key.substr(0, firstSeparatorIndex);
std::string rest = key.substr(firstSeparatorIndex + 1); std::string rest = key.substr(firstSeparatorIndex + 1);
auto it = m_subSections.find(thisKey); auto it = m_subSections.find(thisKey);
if (it == m_subSections.end()) if (it == m_subSections.end())
return EResult::SectionNotPresent; return EResult::SectionNotPresent;
return it->second->GetValue(rest, target); return it->second->GetValue(rest, target);
} }
auto it = m_values.find(key); auto it = m_values.find(key);
if (it == m_values.end()) if (it == m_values.end())
return EResult::KeyNotPresent; return EResult::KeyNotPresent;
@@ -203,14 +255,34 @@ namespace minipp
EResult SetValue(const std::string& name, std::unique_ptr<ValueDataType> value, bool allowOverwrite = false) noexcept EResult SetValue(const std::string& name, std::unique_ptr<ValueDataType> value, bool allowOverwrite = false) noexcept
{ {
static_assert(std::is_base_of<Value, ValueDataType>::value, "ValueDataType must be a subclass of Value"); static_assert(std::is_base_of<Value, ValueDataType>::value, "ValueDataType must be a subclass of Value");
bool overwritten = false;
if (m_values.find(name) != m_values.end()) if (m_values.find(name) != m_values.end())
if (!allowOverwrite) if (!allowOverwrite)
return EResult::KeyAlreadyPresent; return EResult::KeyAlreadyPresent;
else else
{
delete m_values[name]; delete m_values[name];
overwritten = true;
}
m_values[name] = value.release(); m_values[name] = value.release();
return EResult::Success; return overwritten ? EResult::ValueOverwritten : EResult::Success;
}
template<typename ValueDataType>
typename ValueDataType::BaseType GetValueOrDefault(const std::string& key,
const typename ValueDataType::BaseType& defaultValue = typename ValueDataType::BaseType{})
{
static_assert(std::is_base_of<Value, ValueDataType>::value, "ValueDataType must be a subclass of Value");
ValueDataType* value = nullptr;
if (GetValue(key, &value) != EResult::Success)
return defaultValue;
auto casted = dynamic_cast<ValueDataType*>(value);
if (casted == nullptr)
return defaultValue;
return casted->GetValue();
} }
public: public:
@@ -225,12 +297,13 @@ namespace minipp
Section m_rootSection{}; Section m_rootSection{};
private: private:
static std::unique_ptr<Value> ParseValue(std::string value);
static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept; static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept;
public: public:
EResult Parse(const std::string& path) noexcept; EResult Parse(const std::string& path, bool additional = false) noexcept;
EResult Parse(std::ifstream& ifs, bool additional = false) noexcept;
EResult Write(const std::string& path) const noexcept; EResult Write(const std::string& path) const noexcept;
EResult Write(std::ofstream& ofs) const noexcept;
public: public:
const Section& GetRoot() const noexcept { return m_rootSection; } const Section& GetRoot() const noexcept { return m_rootSection; }
@@ -262,32 +335,90 @@ namespace minipp
#include <fstream> #include <fstream>
#include <typeinfo> #include <typeinfo>
#include <sstream> #include <sstream>
#include <stdlib.h>
#include <bitset> #include <bitset>
#if MINIPP_ENABLE_DEBUG_OUTPUT #if MINIPP_ENABLE_DEBUG_OUTPUT
#include <iostream> #include <iostream>
#define COUT(msg) std::cout << "[minipp] " << msg << std::endl #define PP_COUT(msg) std::cout << "[minipp] " << msg << std::endl
#else #else
#define COUT(msg) #define PP_COUT(msg)
#endif #endif
#define ASSERT(condition) if (!condition) abort(); #define PP_COUT_SYNTAX_ERROR_LINE(line, msg) PP_COUT(line << ": " << msg)
#define COUT_SYNTAX_ERROR(line, msg) COUT("Error in line " << line << ": " << msg) #define PP_COUT_SYNTAX_ERROR(msg) PP_COUT("Syntax error: " << msg)
std::unique_ptr<minipp::MiniPPFile::Value> minipp::MiniPPFile::Value::ParseValue(std::string value, EResult* result)
{
#define RETURN_NULLPTR_WITH_RESULT(r) { if (result != nullptr) *result = r; return nullptr; }
char valueFirstChar = value.front();
char valueLastChar = value.back();
if (valueFirstChar == '"')
{
if (valueLastChar != '"')
RETURN_NULLPTR_WITH_RESULT(EResult::MissingQuote);
// 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_WITH_RESULT(parseResult);
return strValue;
}
else if (valueLastChar == 'e')
{
auto boolValue = std::make_unique<Values::BooleanValue>();
auto parseResult = boolValue->Parse(value);
if (!IsResultOk(parseResult))
RETURN_NULLPTR_WITH_RESULT(parseResult);
return boolValue;
}
else if (valueLastChar == 'f')
{
auto floatValue = std::make_unique<Values::FloatValue>();
auto parseResult = floatValue->Parse(value);
if (!IsResultOk(parseResult))
RETURN_NULLPTR_WITH_RESULT(parseResult);
return floatValue;
}
else if (valueLastChar == ']')
{
auto arrayValue = std::make_unique<Values::ArrayValue>();
auto parseResult = arrayValue->Parse(value);
if (!IsResultOk(parseResult))
RETURN_NULLPTR_WITH_RESULT(parseResult);
return arrayValue;
}
else
{
auto intValue = std::make_unique<Values::IntValue>();
auto parseResult = intValue->Parse(value);
if (!IsResultOk(parseResult))
RETURN_NULLPTR_WITH_RESULT(parseResult);
return intValue;
}
}
#pragma region Value Types #pragma region Value Types
minipp::EResult minipp::MiniPPFile::Values::StringValue::Parse(const std::string& str) noexcept minipp::EResult minipp::MiniPPFile::Values::StringValue::Parse(const std::string& str) noexcept
{ {
m_value = ""; m_value = "";
for (size_t i = 0; i < str.size(); ++i) for (size_t i = 0; i < str.size(); ++i)
{ {
if (str[i] == '\\') if (str[i] == '\\')
{ {
if (i + 1 >= str.size()) if (i + 1 >= str.size())
{ {
COUT("Syntax error: '\\' at end of string"); PP_COUT_SYNTAX_ERROR("'\\' at end of string");
return EResult::FormatError; return EResult::BadEscapeSequence;
} }
switch (str[i + 1]) switch (str[i + 1])
@@ -308,22 +439,24 @@ minipp::EResult minipp::MiniPPFile::Values::StringValue::Parse(const std::string
m_value.push_back('\\'); m_value.push_back('\\');
break; break;
default: default:
COUT("Syntax error: Unknown escape sequence '\\" << str[i + 1] << "'"); PP_COUT_SYNTAX_ERROR("Unknown escape sequence '\\" << str[i + 1] << "'");
return EResult::FormatError; return EResult::UnknownEscapeSequence;
} }
++i; ++i;
} }
else if (str[i] == '"') else if (str[i] == '"')
return EResult::FormatError; return EResult::UnescapedStringValue;
else else
m_value.push_back(str[i]); m_value.push_back(str[i]);
} }
return EResult::Success; return EResult::Success;
} }
minipp::EResult minipp::MiniPPFile::Values::StringValue::ToString(std::string& destination) const noexcept minipp::EResult minipp::MiniPPFile::Values::StringValue::ToString(std::string& destination) const noexcept
{ {
std::string sanitizedValue = m_value; std::string sanitizedValue = m_value;
for (size_t i = 0; i < sanitizedValue.size(); ++i) for (size_t i = 0; i < sanitizedValue.size(); ++i)
{ {
switch (sanitizedValue[i]) switch (sanitizedValue[i])
@@ -348,6 +481,7 @@ minipp::EResult minipp::MiniPPFile::Values::StringValue::ToString(std::string& d
sanitizedValue.insert(i, 1, '\\'); sanitizedValue.insert(i, 1, '\\');
i++; i++;
break; break;
default: break;
} }
} }
@@ -361,7 +495,7 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& s
Tools::RemoveAll(sanitizedValue, '_'); Tools::RemoveAll(sanitizedValue, '_');
if (sanitizedValue.empty()) if (sanitizedValue.empty())
{ {
COUT("Empty string for integer value."); PP_COUT_SYNTAX_ERROR("Empty string for integer value.");
return EResult::FormatError; return EResult::FormatError;
} }
@@ -383,8 +517,8 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& s
{ {
if (!Tools::IsIntegerDecimal(sanitizedValue)) if (!Tools::IsIntegerDecimal(sanitizedValue))
{ {
COUT("Invalid decimal integer value: " << sanitizedValue); PP_COUT_SYNTAX_ERROR("Invalid decimal integer value: " << sanitizedValue);
return EResult::FormatError; return EResult::IntegerValueInvalid;
} }
m_value = std::stoll(sanitizedValue); m_value = std::stoll(sanitizedValue);
m_style = EIntStyle::Decimal; m_style = EIntStyle::Decimal;
@@ -392,13 +526,13 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& s
} }
catch (const std::invalid_argument&) catch (const std::invalid_argument&)
{ {
COUT("Invalid integer value: " << sanitizedValue); PP_COUT_SYNTAX_ERROR("Invalid integer value: " << sanitizedValue);
return EResult::FormatError; return EResult::IntegerValueInvalid;
} }
catch (const std::out_of_range&) catch (const std::out_of_range&)
{ {
COUT("Integer value out of range: " << sanitizedValue); PP_COUT_SYNTAX_ERROR("Integer value out of range: " << sanitizedValue);
return EResult::FormatError; return EResult::IntegerValueOutOfRange;
} }
return EResult::Success; return EResult::Success;
@@ -424,6 +558,7 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::ToString(std::string& dest
{ {
std::bitset<64> bs(m_value); std::bitset<64> bs(m_value);
destination = bs.to_string() + "b"; destination = bs.to_string() + "b";
size_t i; size_t i;
for (i = 0; i < destination.size(); ++i) // cut of leading zeros for (i = 0; i < destination.size(); ++i) // cut of leading zeros
if (destination[i] == '1') if (destination[i] == '1')
@@ -436,8 +571,8 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::ToString(std::string& dest
break; break;
} }
default: default:
COUT("Invalid integer style."); PP_COUT_SYNTAX_ERROR("Invalid integer style.");
return EResult::FormatError; return EResult::IntegerStyleInvalid;
} }
return EResult::Success; return EResult::Success;
@@ -451,8 +586,8 @@ minipp::EResult minipp::MiniPPFile::Values::BooleanValue::Parse(const std::strin
m_value = false; m_value = false;
else else
{ {
COUT("Invalid boolean value: " << str << " (may only contain lowercase true and false)"); PP_COUT_SYNTAX_ERROR("Invalid boolean value: " << str << " (may only contain lowercase true and false)");
return EResult::FormatError; return EResult::BooleanValueInvalid;
} }
return EResult::Success; return EResult::Success;
} }
@@ -465,27 +600,38 @@ minipp::EResult minipp::MiniPPFile::Values::BooleanValue::ToString(std::string&
minipp::EResult minipp::MiniPPFile::Values::FloatValue::Parse(const std::string& str) noexcept minipp::EResult minipp::MiniPPFile::Values::FloatValue::Parse(const std::string& str) noexcept
{ {
try
{
m_value = std::stod(str); m_value = std::stod(str);
}
catch (...)
{
PP_COUT_SYNTAX_ERROR("Invalid float value: " << str);
return EResult::FloatValueInvalid;
}
return EResult::Success; return EResult::Success;
} }
minipp::EResult minipp::MiniPPFile::Values::FloatValue::ToString(std::string& destination) const noexcept minipp::EResult minipp::MiniPPFile::Values::FloatValue::ToString(std::string& destination) const noexcept
{ {
destination = std::to_string(m_value); destination = std::to_string(m_value) + "f";
return EResult::Success; return EResult::Success;
} }
minipp::MiniPPFile::Values::ArrayValue::~ArrayValue()
{
for (auto& val : m_values)
delete val;
}
minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string& str) noexcept minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string& str) noexcept
{ {
std::string nValue = str;
if (str.front() != '[' || str.back() != ']') if (str.front() != '[' || str.back() != ']')
{ {
COUT("Array value must be enclosed in [] brackets."); PP_COUT_SYNTAX_ERROR("Array value must be enclosed in [] brackets.");
return EResult::FormatError; return EResult::FormatError;
} }
nValue = str.substr(1, str.size() - 2);
if (nValue.empty())
return EResult::Success;
int64_t bracketCounter = 0; int64_t bracketCounter = 0;
bool isInString = false; // we may encounter array value separators "," inside strings; we need to ignore those bool isInString = false; // we may encounter array value separators "," inside strings; we need to ignore those
@@ -500,7 +646,16 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
if (isInString) if (isInString)
{ {
if (c == '\\') if (c == '\\')
{
if (i + 1 >= str.size())
{
PP_COUT_SYNTAX_ERROR("Bad escape sequence: '\\' at end of string");
return EResult::BadEscapeSequence;
}
currentElement += c;
currentElement += str[i + 1];
++i; ++i;
}
else if (c == '"') else if (c == '"')
{ {
isInString = false; isInString = false;
@@ -520,6 +675,7 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
{ {
if (c == '[') if (c == '[')
{ {
// TODO: This would not be allowed by standard. Add EXT_ALLOW_MULTIDIMENSIONAL_ARRAY
if (++bracketCounter > 1) if (++bracketCounter > 1)
currentElement += c; currentElement += c;
} }
@@ -528,8 +684,8 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
--bracketCounter; --bracketCounter;
if (bracketCounter < 0) if (bracketCounter < 0)
{ {
COUT("Array brackets are not balanced. (One ] too much or encountered too early)"); PP_COUT_SYNTAX_ERROR("Array brackets are not balanced. (One ] too much or encountered too early)");
return EResult::FormatError; return EResult::ArrayBracketsInbalanced;
} }
else if (bracketCounter >= 1) else if (bracketCounter >= 1)
currentElement += c; currentElement += c;
@@ -546,8 +702,8 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
if (bracketCounter != 0) // will always be a positive value because negative values are caught earlier if (bracketCounter != 0) // will always be a positive value because negative values are caught earlier
{ {
COUT("Array brackets are not balanced. (Missing " << bracketCounter << " closing brackets)"); PP_COUT_SYNTAX_ERROR("Array brackets are not balanced. (Missing " << bracketCounter << " closing brackets)");
return EResult::FormatError; return EResult::ArrayBracketsInbalanced;
} }
if (!currentElement.empty()) if (!currentElement.empty())
@@ -555,11 +711,12 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
size_t lastTypeIdHash = 0; size_t lastTypeIdHash = 0;
bool hasTypeHash = false; bool hasTypeHash = false;
EResult result;
for (auto& elem : elements) for (auto& elem : elements)
{ {
auto parsed = ParseValue(elem); auto parsed = ParseValue(elem, &result);
if (parsed == nullptr) if (parsed == nullptr)
return EResult::FormatError; return result;
if (!hasTypeHash) if (!hasTypeHash)
{ {
@@ -569,8 +726,9 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string&
else if (typeid(*parsed).hash_code() != lastTypeIdHash) else if (typeid(*parsed).hash_code() != lastTypeIdHash)
return EResult::ArrayDataTypeInconsistency; return EResult::ArrayDataTypeInconsistency;
m_values.push_back(std::move(parsed)); m_values.push_back(parsed.release());
} }
return EResult::Success; return EResult::Success;
} }
@@ -602,6 +760,7 @@ minipp::EResult minipp::MiniPPFile::Values::ArrayValue::ToString(std::string& de
valueString = valueString.substr(0, valueString.size() - 2); valueString = valueString.substr(0, valueString.size() - 2);
destination = "[" + valueString + "]"; destination = "[" + valueString + "]";
return EResult::Success; return EResult::Success;
} }
@@ -629,7 +788,7 @@ minipp::EResult minipp::MiniPPFile::Section::GetSubSection(const std::string& ke
auto it = m_subSections.find(thisKey); auto it = m_subSections.find(thisKey);
if (it == m_subSections.end()) if (it == m_subSections.end())
{ {
COUT("Sub-Section not found: " << thisKey); PP_COUT_SYNTAX_ERROR("Sub-Section not found: " << thisKey);
return EResult::SectionNotPresent; return EResult::SectionNotPresent;
} }
if (rest.empty()) if (rest.empty())
@@ -643,70 +802,31 @@ minipp::EResult minipp::MiniPPFile::Section::GetSubSection(const std::string& ke
minipp::EResult minipp::MiniPPFile::Section::SetSubSection(const std::string& name, std::unique_ptr<Section> value, bool allowOverwrite) noexcept 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()) auto pathIndex = name.find('.');
std::string thisName = name;
std::string rest{};
if (pathIndex != std::string::npos)
{
thisName = name.substr(0, pathIndex);
rest = name.substr(pathIndex + 1);
}
if (rest.empty())
{
if (m_subSections.find(thisName) != m_subSections.end())
if (!allowOverwrite) if (!allowOverwrite)
return EResult::SectionAlreadyPresent; return EResult::SectionAlreadyPresent;
else else
delete m_subSections[name]; delete m_subSections[thisName];
m_subSections[name] = value.release(); m_subSections[thisName] = value.release();
return EResult::Success; 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; if (m_subSections.find(thisName) == m_subSections.end())
} m_subSections[thisName] = new Section();
else if (valueLastChar == 'f')
{
auto floatValue = std::make_unique<Values::FloatValue>();
auto parseResult = floatValue->Parse(value);
if (!IsResultOk(parseResult))
return nullptr;
return floatValue; return m_subSections[thisName]->SetSubSection(rest, std::move(value), allowOverwrite);
}
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, std::string partTreeName) noexcept minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept
@@ -718,12 +838,12 @@ minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::of
{ {
if (!Tools::IsNameValid(pair.first)) if (!Tools::IsNameValid(pair.first))
{ {
COUT("Invalid name for key: " << pair.first); PP_COUT_SYNTAX_ERROR("Invalid name for key: " << pair.first);
return EResult::FormatError; return EResult::InvalidName;
} }
for (const auto& comment : pair.second->m_comments) for (const auto& comment : pair.second->m_comments)
ofs << comment << std::endl; ofs << "# " << comment << std::endl;
ofs << pair.first << " = "; ofs << pair.first << " = ";
auto result = pair.second->ToString(valueString); auto result = pair.second->ToString(valueString);
@@ -733,6 +853,7 @@ minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::of
} }
ofs << std::endl; ofs << std::endl;
} }
if (!partTreeName.empty()) if (!partTreeName.empty())
partTreeName += "."; partTreeName += ".";
@@ -740,8 +861,8 @@ minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::of
{ {
if (!Tools::IsNameValid(pair.first)) if (!Tools::IsNameValid(pair.first))
{ {
COUT("Invalid name for section: " << pair.first); PP_COUT_SYNTAX_ERROR("Invalid name for section: " << pair.first);
return EResult::FormatError; return EResult::InvalidName;
} }
for (const auto& comment : pair.second->m_comments) for (const auto& comment : pair.second->m_comments)
@@ -756,10 +877,23 @@ minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::of
return EResult::Success; return EResult::Success;
} }
minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept minipp::EResult minipp::MiniPPFile::Parse(const std::string& path, bool additional) noexcept
{ {
std::ifstream ifs; std::ifstream ifs;
ifs.open(path); ifs.open(path);
return Parse(ifs, additional);
}
minipp::EResult minipp::MiniPPFile::Parse(std::ifstream& ifs, bool additional) noexcept
{
#define PP_COUT_HERE() PP_COUT_SYNTAX_ERROR_LINE(lineCounter, currentLine << " <- HERE");
if (!additional)
{
m_rootSection.m_comments.clear();
m_rootSection.m_values.clear();
m_rootSection.m_subSections.clear();
}
if (!ifs.is_open()) if (!ifs.is_open())
return EResult::FileIOError; return EResult::FileIOError;
@@ -788,38 +922,41 @@ minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept
{ {
if (lastChar != ']') if (lastChar != ']')
{ {
COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line."); PP_COUT_SYNTAX_ERROR("Expected ']' at the end of the line.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::SectionExpectedClosingBracket;
} }
std::string sectionName = currentLine.substr(1, currentLine.size() - 2); std::string sectionPathStr = currentLine.substr(1, currentLine.size() - 2);
Tools::StringTrim(sectionName); Tools::StringTrim(sectionPathStr);
if (sectionName.empty()) if (sectionPathStr.empty())
{ {
COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation."); PP_COUT_SYNTAX_ERROR("Expected section path. Found empty section begin notation.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::EmptySectionName;
} }
// Create section tree // Create section tree
Section* ubSection = &m_rootSection; Section* ubSection = &m_rootSection;
std::vector<std::string> sectionPath = Tools::SplitByDelimiter(sectionName, '.'); std::vector<std::string> sectionPath = Tools::SplitByDelimiter(sectionPathStr, '.');
EResult result;
for (size_t i = 0; i < sectionPath.size(); ++i) for (size_t i = 0; i < sectionPath.size(); ++i)
{ {
const std::string& sectionName = sectionPath[i]; const std::string& sectionName = sectionPath[i];
if (!Tools::IsNameValid(sectionName)) if (!Tools::IsNameValid(sectionName))
{ {
COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _."); PP_COUT_SYNTAX_ERROR("Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::InvalidName;
} }
EResult result;
result = ubSection->SetSubSection(sectionName, std::make_unique<Section>(), false); result = ubSection->SetSubSection(sectionName, std::make_unique<Section>(), false);
if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1) if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1)
{ {
COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once."); PP_COUT_SYNTAX_ERROR("All (sub-) sections may only be defined once.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::SectionAlreadyPresent;
} }
result = ubSection->GetSubSection(sectionName, &ubSection); ubSection->GetSubSection(sectionName, &ubSection);
ASSERT(IsResultOk(result)); // should never happen
} }
currentSection = ubSection; currentSection = ubSection;
currentSection->m_comments = commentBuffer; currentSection->m_comments = commentBuffer;
@@ -828,40 +965,59 @@ minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept
} }
if (currentSection == nullptr) if (currentSection == nullptr)
{ {
COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair."); PP_COUT_SYNTAX_ERROR("Expected section begin before key-value pair.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::KeyValuePairNotInSection;
} }
int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '='); int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '=');
if (keyValueDelimiterIndex == -1) if (keyValueDelimiterIndex == -1)
{ {
COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line."); PP_COUT_SYNTAX_ERROR("Expected '=' in line.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::ExpectedKeyValuePair;
} }
auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex); auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex);
Tools::StringTrim(keyValuePair.first); Tools::StringTrim(keyValuePair.first);
Tools::StringTrim(keyValuePair.second); Tools::StringTrim(keyValuePair.second);
if (keyValuePair.first.empty()) if (keyValuePair.first.empty())
{ {
COUT_SYNTAX_ERROR(lineCounter, "Expected key in line."); PP_COUT_SYNTAX_ERROR("Expected key in line.");
return EResult::FormatError; PP_COUT_HERE();
return EResult::KeyEmpty;
} }
if (!Tools::IsNameValid(keyValuePair.first))
{
PP_COUT_SYNTAX_ERROR("Invalid key name. (\"" << keyValuePair.first << "\") May only contain [a - z][A - Z][0 - 9] and _.");
PP_COUT_HERE();
return EResult::InvalidName;
}
if (keyValuePair.second.empty()) if (keyValuePair.second.empty())
{ {
COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed"); PP_COUT_SYNTAX_ERROR("Empty keys are not allowed");
return EResult::FormatError; PP_COUT_HERE();
return EResult::ValueEmpty;
} }
auto parsedValue = ParseValue(keyValuePair.second); EResult parseResult;
auto parsedValue = Value::ParseValue(keyValuePair.second, &parseResult);
if (parsedValue == nullptr) if (parsedValue == nullptr)
{ {
COUT_SYNTAX_ERROR(lineCounter, "Invalid value"); PP_COUT_HERE();
return EResult::FormatError; return parseResult;
} }
parsedValue->m_comments = commentBuffer; parsedValue->m_comments = commentBuffer;
commentBuffer.clear(); commentBuffer.clear();
currentSection->SetValue(keyValuePair.first, std::move(parsedValue), false); auto valueSetResult = currentSection->SetValue(keyValuePair.first, std::move(parsedValue), false);
if (valueSetResult != EResult::Success)
{
PP_COUT_SYNTAX_ERROR("Key already present: " << keyValuePair.first);
PP_COUT_HERE();
return valueSetResult;
}
} }
return EResult::Success; return EResult::Success;
@@ -871,6 +1027,12 @@ minipp::EResult minipp::MiniPPFile::Write(const std::string& path) const noexcep
{ {
std::ofstream ofs; std::ofstream ofs;
ofs.open(path); ofs.open(path);
return Write(ofs);
}
minipp::EResult minipp::MiniPPFile::Write(std::ofstream& ofs) const noexcept
{
if (!ofs.is_open()) if (!ofs.is_open())
return EResult::FileIOError; return EResult::FileIOError;
@@ -891,6 +1053,7 @@ bool minipp::MiniPPFile::Tools::StringStartsWith(const std::string& str, const s
for (size_t i = 0; i < beg.size(); ++i) for (size_t i = 0; i < beg.size(); ++i)
if (str[i] != beg[i]) if (str[i] != beg[i])
return false; return false;
return true; return true;
} }
@@ -902,6 +1065,7 @@ bool minipp::MiniPPFile::Tools::StringEndsWith(const std::string& str, const std
for (size_t i = 0; i < end.size(); ++i) for (size_t i = 0; i < end.size(); ++i)
if (str[str.size() - i - 1] != end[end.size() - i - 1]) if (str[str.size() - i - 1] != end[end.size() - i - 1])
return false; return false;
return true; return true;
} }
@@ -909,6 +1073,7 @@ void minipp::MiniPPFile::Tools::StringTrim(std::string& str)
{ {
if (str.empty()) if (str.empty())
return; return;
size_t start = 0; size_t start = 0;
size_t end = str.size() - 1; size_t end = str.size() - 1;
while (str[start] == ' ' || str[start] == '\t') while (str[start] == ' ' || str[start] == '\t')
@@ -930,6 +1095,7 @@ bool minipp::MiniPPFile::Tools::IsNameValid(const std::string& name) noexcept
continue; continue;
return false; return false;
} }
return true; return true;
} }
@@ -942,6 +1108,7 @@ int64_t minipp::MiniPPFile::Tools::FirstIndexOf(const std::string& str, char c)
return index; return index;
++index; ++index;
} }
return -1; return -1;
} }
@@ -954,6 +1121,7 @@ int64_t minipp::MiniPPFile::Tools::LastIndexOf(const std::string& str, char c) n
return index; return index;
--index; --index;
} }
return -1; return -1;
} }
@@ -980,6 +1148,7 @@ std::vector<std::string> minipp::MiniPPFile::Tools::SplitByDelimiter(const std::
if (!tmp.empty()) if (!tmp.empty())
elements.push_back(tmp); elements.push_back(tmp);
return elements; return elements;
} }
@@ -993,6 +1162,7 @@ bool minipp::MiniPPFile::Tools::IsIntegerDecimal(const std::string& str) noexcep
for (size_t i = 0; i < str.size(); ++i) for (size_t i = 0; i < str.size(); ++i)
if (str[i] < '0' || str[i] > '9') if (str[i] < '0' || str[i] > '9')
return false; return false;
return true; return true;
} }

2
minipp/simple.mini Normal file
View File

@@ -0,0 +1,2 @@
[game]
what=["test\t\\yeah", "hai"]

View File

@@ -42,5 +42,6 @@ int main()
result = file.Write("test_out.mini"); result = file.Write("test_out.mini");
int64_t test = root.GetValueOrDefault<MiniPPFile::Values::IntValue>("game.year", 1999);
return 0; return 0;
} }

View File

@@ -1,10 +1,13 @@
[game] [game]
name = "Test Game" name = "Test Game\nNext Line"
version = "1.0.0" version = "1.0.0"
year = 2025 year = 2025
completionPercentage = 50.0f completionPercentage = 50.0f
# Should only be true if completionPercentage is 100 # Should only be true if completionPercentage is 100
is_completed = false is_completed = false
testargs = ["this is a \\\"test\"", "this is\n the next line"]
testTestArg = [["yeah", "new\nline"], ["hallo\ttest\n\\\\"]]
testEmpty = []
# This section is about # This section is about
# the settings of a game window # the settings of a game window

View File

@@ -1,7 +1,10 @@
[game] [game]
name = "Test Game" testEmpty = []
name = "Test Game\nNext Line"
testTestArg = [["yeah", "new\nline"], ["hallo\ttest\n\\\\"]]
completionPercentage = 50.000000 completionPercentage = 50.000000
version = "1.0.0" version = "1.0.0"
testargs = ["this is a \\\"test\"", "this is\n the next line"]
year = 2025 year = 2025
# Should only be true if completionPercentage is 100 # Should only be true if completionPercentage is 100
is_completed = false is_completed = false