diff --git a/CMakeLists.txt b/CMakeLists.txt index b0b56db..5a4fdb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5) add_subdirectory("third/Dict") -project(blet_conf VERSION 1.0.0 LANGUAGES CXX) +project(blet_conf VERSION 2.0.0 LANGUAGES CXX) # OPTIONS option(BUILD_EXAMPLE "Build example binaries" OFF) @@ -23,7 +23,9 @@ if(NOT CMAKE_INSTALL_INCLUDEDIR) endif() add_library("${PROJECT_NAME}" - "${CMAKE_CURRENT_SOURCE_DIR}/src/conf.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/dump.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/exception.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/load.cpp" ) set_target_properties("${PROJECT_NAME}" diff --git a/README.md b/README.md index 929bad3..4deeb36 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,12 @@ parents[] = { } ] } +# json parents[] = { - name = toto - age = 0x2A - gender = M - childs = [] + "name": "toto", + "age": 0x2A, + "gender": "M", + "childs": [] } ; pseudo json one line parents[] = { name = clara, age = 42, gender = F, childs = [], emptyObj = {} } @@ -216,14 +217,21 @@ std::cout << blet::conf::dump(conf, 2, ' ', blet::conf::JSON_STYLE) << std::endl ## Build ```bash -# static build -mkdir build ; pushd build && cmake .. && make -j ; popd -# shared build -mkdir build ; pushd build && cmake -DBUILD_SHARED_LIBS=1 .. && make -j ; popd -# static build and launch test -mkdir build ; pushd build && cmake -DBUILD_TESTING=1 .. && make -j && make test ; popd -# shared build and launch test -mkdir build ; pushd build && cmake -DBUILD_SHARED_LIBS=1 -DBUILD_TESTING=1 .. && make -j && make test ; popd +# Static Release +mkdir build; pushd build; cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 .. && make -j && make install; popd +# Dynamic Release +mkdir build; pushd build; cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=1 .. && make -j && make install; popd + +# Static Release C++98 +mkdir build; pushd build; cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=98 -DBUILD_SHARED_LIBS=0 .. && make -j && make install; popd +# Dynamic Release C++98 +mkdir build; pushd build; cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=98 -DBUILD_SHARED_LIBS=1 .. && make -j && make install; popd + +# Install with custom directory +mkdir build; pushd build; cmake -DCMAKE_INSTALL_PREFIX="YOUR_INSTALL_PATH" .. && make -j && make install; popd + +# Example + Tests + Coverage +mkdir build; pushd build; cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_EXAMPLE=1 -DBUILD_TESTING=1 -DBUILD_COVERAGE=1 -DCMAKE_CXX_STANDARD=98 .. && make -j && make test -j; popd ``` ## Load Functions diff --git a/docs/examples.md b/docs/examples.md index 5aa2c06..88c9ce3 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -122,6 +122,48 @@ std::cout << conf["test"]["42 space "]["key[]"] << std::endl; // value space ``` +## Json + +```conf +$ cat ./test6.conf +# comment +{ + "test": { + "key": 42, # comment line + "42": "value" + } +} +``` +```cpp +blet::Dict conf = blet::conf::loadFile("./test6.conf"); +std::cout << conf["test"]["key"] << std::endl; +std::cout << conf["test"]["42"] << std::endl; +// output: +// 42 +// value +``` + +## Pseudo Json + +```conf +$ cat ./test7.conf +# comment +{ + test = { + key = 42 # comment line + 42 = "value" + } +} +``` +```cpp +blet::Dict conf = blet::conf::loadFile("./test7.conf"); +std::cout << conf["test"]["key"] << std::endl; +std::cout << conf["test"]["42"] << std::endl; +// output: +// 42 +// value +``` + ## loadFile ```conf diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 4823c27..ce0a5b1 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -20,5 +20,5 @@ foreach(file ${example_files}) NO_SYSTEM_FROM_IMPORTED ON COMPILE_FLAGS "-pedantic -Wall -Wextra -Werror" ) - target_link_libraries("${filenamewe}.${library_project_name}.example" PUBLIC "${library_project_name}_single_include") + target_link_libraries("${filenamewe}.${library_project_name}.example" PUBLIC "${library_project_name}") endforeach() \ No newline at end of file diff --git a/example/quickstart.conf b/example/quickstart.conf index 5c91c59..b4213cb 100644 --- a/example/quickstart.conf +++ b/example/quickstart.conf @@ -47,11 +47,12 @@ parents[] = { } ] } +# json parents[] = { - name = toto - age = 0x2A - gender = M - childs = [] + "name": "toto", + "age": 0x2A, + "gender": "M", + "childs": [] } ; pseudo json one line parents[] = { name = clara, age = 42, gender = F, childs = [], emptyObj = {} } \ No newline at end of file diff --git a/include/blet/conf.h b/include/blet/conf.h index 01c04f3..2f24ad3 100644 --- a/include/blet/conf.h +++ b/include/blet/conf.h @@ -37,13 +37,13 @@ namespace blet { namespace conf { /** - * @brief Parse exception from std::exception + * @brief Load exception from std::exception */ class LoadException : public std::exception { public: LoadException(const std::string& filename, const std::string& message); LoadException(const std::string& filename, std::size_t line, std::size_t column, const std::string& message); - virtual ~LoadException() throw(); + ~LoadException() throw(); const char* what() const throw(); const std::string& filename() const throw(); const std::string& message() const throw(); @@ -88,7 +88,7 @@ std::string dump(const blet::Dict& dict, std::size_t indent = 0, char indentChar enum EDumpStyle style = CONF_STYLE); /** - * @brief Parse and load a config from filename. + * @brief Load a config from filename. * * @param filename A filename. * @return blet::Dict Dictionnary of config. @@ -96,7 +96,7 @@ std::string dump(const blet::Dict& dict, std::size_t indent = 0, char indentChar blet::Dict loadFile(const char* filename); /** - * @brief Parse and load a config from stream. + * @brief Load a config from stream. * * @param stream A stream. * @return blet::Dict Dictionnary of config. @@ -104,7 +104,7 @@ blet::Dict loadFile(const char* filename); blet::Dict loadStream(std::istream& stream); /** - * @brief Parse and load a config from string. + * @brief Load a config from string. * * @param str A string. * @return blet::Dict Dictionnary of config. @@ -112,7 +112,7 @@ blet::Dict loadStream(std::istream& stream); blet::Dict loadString(const std::string& str); /** - * @brief Parse and load a config from data. + * @brief Load a config from data. * * @param data A data. * @param size Size of data. diff --git a/single_include/amalgamate_config.json b/single_include/amalgamate_config.json index 99d7efe..e381807 100644 --- a/single_include/amalgamate_config.json +++ b/single_include/amalgamate_config.json @@ -7,6 +7,8 @@ ], "sources": [ "include/blet/conf.h", - "src/conf.cpp" + "src/dump.cpp", + "src/exception.cpp", + "src/load.cpp" ] } diff --git a/single_include/blet/conf.h b/single_include/blet/conf.h index 31b9472..92ba0d9 100644 --- a/single_include/blet/conf.h +++ b/single_include/blet/conf.h @@ -5960,13 +5960,13 @@ namespace blet { namespace conf { /** - * @brief Parse exception from std::exception + * @brief Load exception from std::exception */ class LoadException : public std::exception { public: LoadException(const std::string& filename, const std::string& message); LoadException(const std::string& filename, std::size_t line, std::size_t column, const std::string& message); - virtual ~LoadException() throw(); + ~LoadException() throw(); const char* what() const throw(); const std::string& filename() const throw(); const std::string& message() const throw(); @@ -6011,7 +6011,7 @@ std::string dump(const blet::Dict& dict, std::size_t indent = 0, char indentChar enum EDumpStyle style = CONF_STYLE); /** - * @brief Parse and load a config from filename. + * @brief Load a config from filename. * * @param filename A filename. * @return blet::Dict Dictionnary of config. @@ -6019,7 +6019,7 @@ std::string dump(const blet::Dict& dict, std::size_t indent = 0, char indentChar blet::Dict loadFile(const char* filename); /** - * @brief Parse and load a config from stream. + * @brief Load a config from stream. * * @param stream A stream. * @return blet::Dict Dictionnary of config. @@ -6027,7 +6027,7 @@ blet::Dict loadFile(const char* filename); blet::Dict loadStream(std::istream& stream); /** - * @brief Parse and load a config from string. + * @brief Load a config from string. * * @param str A string. * @return blet::Dict Dictionnary of config. @@ -6035,7 +6035,7 @@ blet::Dict loadStream(std::istream& stream); blet::Dict loadString(const std::string& str); /** - * @brief Parse and load a config from data. + * @brief Load a config from data. * * @param data A data. * @param size Size of data. @@ -6054,13 +6054,13 @@ blet::Dict loadData(const void* data, std::size_t size); // ----------------------- // ------------------ -// Start src/conf.cpp +// Start src/dump.cpp // ------------------ /** - * conf.cpp + * dump.cpp * * Licensed under the MIT License . - * Copyright (c) 2022-2023 BLET Mickaël. + * Copyright (c) 2024 BLET Mickaël. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -6081,96 +6081,15 @@ blet::Dict loadData(const void* data, std::size_t size); * SOFTWARE. */ -// #include "blet/conf.h" (already included) - -#include -#include -#include - -#include // std::ifstream #include // std::setprecision #include // std::numeric_limits +// #include "blet/conf.h" (already included) + namespace blet { namespace conf { -const char replaceBackslash = -42; - -inline LoadException::LoadException(const std::string& filename, const std::string& message) : - std::exception(), - filename_(filename), - message_(message), - line_(0), - column_(0) { - std::ostringstream oss(""); - oss << "Parse "; - if (!filename_.empty()) { - oss << filename_ << ": "; - } - oss << '(' << message_ << ")"; - what_ = oss.str(); -} - -inline LoadException::LoadException(const std::string& filename, std::size_t line, std::size_t column, - const std::string& message) : - std::exception(), - filename_(filename), - message_(message), - line_(line), - column_(column) { - std::ostringstream oss(""); - oss << "Parse at "; - if (!filename_.empty()) { - oss << filename_ << ':'; - } - oss << line_ << ':' << column_ << " (" << message_ << ")"; - what_ = oss.str(); -} - -inline LoadException::~LoadException() throw() {} - -inline const char* LoadException::what() const throw() { - return what_.c_str(); -} - -inline const std::string& LoadException::filename() const throw() { - return filename_; -} - -inline const std::string& LoadException::message() const throw() { - return message_; -} - -inline const std::size_t& LoadException::line() const throw() { - return line_; -} - -inline const std::size_t& LoadException::column() const throw() { - return column_; -} - -/** - * @brief structure of info dumper - */ -struct ConfDumpInfo { - inline ConfDumpInfo(std::ostream& os_, std::size_t& indent_, char& indentCharacter_, enum EDumpStyle& style_) : - os(os_), - indent(indent_), - indentCharacter(indentCharacter_), - style(style_), - indexSection(0), - index(0) {} - inline ~ConfDumpInfo() {} - - std::ostream& os; - std::size_t& indent; - char& indentCharacter; - enum EDumpStyle& style; - std::size_t indexSection; - std::size_t index; -}; - // ----------------------------------------------------------------------------- // ................................. // .#####...##..##..##...##..#####.. @@ -6181,103 +6100,134 @@ struct ConfDumpInfo { // ................................. // ----------------------------------------------------------------------------- -static inline bool s_forceKeyString(const std::string& str) { - bool ret = false; - if (str.empty()) { - ret = true; - } - else { +class Dumper { + public: + inline Dumper(std::ostream& os, std::size_t indent, char indentCharacter) : + os_(os), + indent_(indent), + indentCharacter_(indentCharacter), + indentIndex_(0) {} + + inline ~Dumper() {} + + inline void stringEscape(const std::string& str) { for (std::size_t i = 0; i < str.size(); ++i) { - if (str[i] == ' ' || str[i] == '=' || str[i] == ',' || str[i] == ';' || str[i] == '#' || str[i] == '[' || - str[i] == ']' || str[i] == '"' || str[i] == '\'' || str[i] == '\a' || str[i] == '\b' || - str[i] == '\f' || str[i] == '\r' || str[i] == '\t' || str[i] == '\v' || str[i] == '\\') { - ret = true; - break; + switch (str[i]) { + case '\a': + os_ << '\\' << 'a'; + break; + case '\b': + os_ << '\\' << 'b'; + break; + case '\f': + os_ << '\\' << 'f'; + break; + case '\n': + os_ << '\\' << 'n'; + break; + case '\r': + os_ << '\\' << 'r'; + break; + case '\t': + os_ << '\\' << 't'; + break; + case '\v': + os_ << '\\' << 'v'; + break; + case '\'': + os_ << '\\' << '\''; + break; + case '"': + os_ << '\\' << '"'; + break; + case '\\': + os_ << '\\' << '\\'; + break; + default: + os_ << str[i]; + break; } } } - return ret; -} -static inline void s_stringEscape(std::ostream& oss, const std::string& str) { - for (std::size_t i = 0; i < str.size(); ++i) { - switch (str[i]) { - case '\a': - oss << '\\' << 'a'; - break; - case '\b': - oss << '\\' << 'b'; - break; - case '\f': - oss << '\\' << 'f'; - break; - case '\n': - oss << '\\' << 'n'; - break; - case '\r': - oss << '\\' << 'r'; - break; - case '\t': - oss << '\\' << 't'; - break; - case '\v': - oss << '\\' << 'v'; - break; - case '\'': - oss << '\\' << '\''; - break; - case '"': - oss << '\\' << '"'; - break; - case '\\': - oss << '\\' << '\\'; - break; - default: - oss << str[i]; - break; + inline void newlineDump(const blet::Dict& dict) { + if (indent_ != 0 && ((dict.isArray() && !dict.getValue().getArray().empty()) || + (dict.isObject() && !dict.getValue().getObject().empty()))) { + os_ << '\n'; } } -} -static inline void s_newlineDump(ConfDumpInfo& info, const blet::Dict& dict) { - if (info.indent != 0) { - if (dict.getType() == blet::Dict::OBJECT_TYPE && !dict.getValue().getObject().empty()) { - info.os << '\n'; - } - if (dict.getType() == blet::Dict::ARRAY_TYPE && !dict.getValue().getArray().empty()) { - info.os << '\n'; + inline void indentDump() { + if (indent_ != 0) { + os_ << std::string(indent_ * indentIndex_, indentCharacter_); } } -} -static inline void s_indentDump(ConfDumpInfo& info) { - if (info.indent != 0) { - info.os << std::string(info.indent * info.index, info.indentCharacter); + inline void nullDump() { + os_ << "null"; } -} -static inline void s_nullDump(ConfDumpInfo& info, const blet::Dict& /*dict*/) { - info.os << "null"; -} + inline void numberDump(const blet::Dict& dict) { + os_ << dict.getValue().getNumber(); + } -static inline void s_numberDump(ConfDumpInfo& info, const blet::Dict& dict) { - info.os << dict.getValue().getNumber(); -} + inline void booleanDump(const blet::Dict& dict) { + if (dict.getValue().getBoolean()) { + os_ << "true"; + } + else { + os_ << "false"; + } + } -static inline void s_booleanDump(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getBoolean()) { - info.os << "true"; + inline void stringDump(const blet::Dict& dict) { + os_ << '"'; + stringEscape(dict.getValue().getString()); + os_ << '"'; } - else { - info.os << "false"; + + protected: + static inline bool forceKeyString(const std::string& str) { + bool ret = false; + if (str.empty()) { + ret = true; + } + else { + for (std::size_t i = 0; ret == false && i < str.size(); ++i) { + switch (str[i]) { + case ' ': + case '=': + case ',': + case ';': + case '#': + case '[': + case ']': + case '{': + case '}': + case '"': + case '\'': + case '\a': + case '\b': + case '\f': + case '\r': + case '\t': + case '\v': + case '\\': + ret = true; + break; + default: + break; + } + } + } + return ret; } -} -static inline void s_stringDump(ConfDumpInfo& info, const blet::Dict& dict) { - info.os << '"'; - s_stringEscape(info.os, dict.getValue().getString()); - info.os << '"'; -} + std::ostream& os_; + std::size_t indent_; + char indentCharacter_; + std::size_t indentIndex_; +}; // ----------------------------------------------------------------------------- // ......................................................................... @@ -6289,152 +6239,152 @@ static inline void s_stringDump(ConfDumpInfo& info, const blet::Dict& dict) { // ......................................................................... // ----------------------------------------------------------------------------- -static void s_jsonDumpType(ConfDumpInfo& info, const blet::Dict& dict); +class JsonDumper : public Dumper { + public: + inline JsonDumper(std::ostream& os, std::size_t indent, char indentCharacter) : + Dumper(os, indent, indentCharacter) {} -static inline void s_jsonDumpObject(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getValue().getObject().empty()) { - info.os << "{}"; + inline ~JsonDumper() {} + + inline void jsonDumpTypeFirst(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + case blet::Dict::BOOLEAN_TYPE: + case blet::Dict::NUMBER_TYPE: + case blet::Dict::STRING_TYPE: + jsonDumpType(dict); + break; + case blet::Dict::ARRAY_TYPE: + os_ << "\"\""; + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + jsonDumpArray(dict); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObjectFirst(dict); + break; + } } - else { - info.os << '{'; - s_newlineDump(info, dict); - ++info.index; + + inline void jsonDumpObjectFirst(const blet::Dict& dict) { for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); cit != dict.getValue().getObject().end(); ++cit) { if (cit != dict.getValue().getObject().begin()) { - info.os << ','; - s_newlineDump(info, dict); + os_ << '\n'; } // key - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; } else { - s_stringEscape(info.os, cit->first); + stringEscape(cit->first); } - if (info.indent > 0) { - info.os << ' '; + if (indent_ > 0) { + os_ << ' '; } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; + os_ << '='; + if (indent_ > 0) { + os_ << ' '; } - s_jsonDumpType(info, cit->second); + jsonDumpType(cit->second); } - --info.index; - s_newlineDump(info, dict); - s_indentDump(info); - info.os << '}'; } -} -static inline void s_jsonDumpArray(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getValue().getArray().empty()) { - info.os << "[]"; - } - else { - info.os << '['; - s_newlineDump(info, dict); - ++info.index; - for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { - if (i > 0) { - info.os << ','; - s_newlineDump(info, dict); - } - // value - s_indentDump(info); - s_jsonDumpType(info, dict.getValue().getArray()[i]); + inline void jsonDumpType(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + nullDump(); + break; + case blet::Dict::BOOLEAN_TYPE: + booleanDump(dict); + break; + case blet::Dict::NUMBER_TYPE: + numberDump(dict); + break; + case blet::Dict::STRING_TYPE: + stringDump(dict); + break; + case blet::Dict::ARRAY_TYPE: + jsonDumpArray(dict); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObject(dict); + break; } - --info.index; - s_newlineDump(info, dict); - s_indentDump(info); - info.os << ']'; - } -} - -static inline void s_jsonDumpType(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - s_jsonDumpArray(info, dict); - break; - case blet::Dict::OBJECT_TYPE: - s_jsonDumpObject(info, dict); - break; } -} -static inline void s_jsonDumpObjectFirst(ConfDumpInfo& info, const blet::Dict& dict) { - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - if (cit != dict.getValue().getObject().begin()) { - info.os << '\n'; - } - // key - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; + inline void jsonDumpArray(const blet::Dict& dict) { + if (dict.getValue().getArray().empty()) { + os_ << "[]"; } else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; + os_ << '['; + newlineDump(dict); + ++indentIndex_; + for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { + if (i > 0) { + os_ << ','; + newlineDump(dict); + } + // value + indentDump(); + jsonDumpType(dict.getValue().getArray()[i]); + } + --indentIndex_; + newlineDump(dict); + indentDump(); + os_ << ']'; } - s_jsonDumpType(info, cit->second); } -} -static inline void s_jsonDumpTypeFirst(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - info.os << "\"\""; - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; + inline void jsonDumpObject(const blet::Dict& dict) { + if (dict.getValue().getObject().empty()) { + os_ << "{}"; + } + else { + os_ << '{'; + newlineDump(dict); + ++indentIndex_; + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + if (cit != dict.getValue().getObject().begin()) { + os_ << ','; + newlineDump(dict); + } + // key + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + jsonDumpType(cit->second); } - s_jsonDumpArray(info, dict); - break; - case blet::Dict::OBJECT_TYPE: - s_jsonDumpObjectFirst(info, dict); - break; + --indentIndex_; + newlineDump(dict); + indentDump(); + os_ << '}'; + } } -} +}; // ----------------------------------------------------------------------------- // ......................................................................... @@ -6446,196 +6396,230 @@ static inline void s_jsonDumpTypeFirst(ConfDumpInfo& info, const blet::Dict& dic // ......................................................................... // ----------------------------------------------------------------------------- -static void s_confDumpType(ConfDumpInfo& info, const blet::Dict& dict); +class ConfDumper : public JsonDumper { + public: + inline ConfDumper(std::ostream& os, std::size_t indent, char indentCharacter) : + JsonDumper(os, indent, indentCharacter), + sectionIndex_(0) {} -static inline void s_confDumpArray(ConfDumpInfo& info, const std::string& key, const blet::Dict& dict) { - if (dict.getValue().getArray().empty()) { - // key - s_indentDump(info); - if (s_forceKeyString(key)) { - info.os << '"'; - s_stringEscape(info.os, key); - info.os << '"'; - } - else { - s_stringEscape(info.os, key); - } - // value operator - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; + inline ~ConfDumper() {} + + inline void confDumpType(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + nullDump(); + break; + case blet::Dict::BOOLEAN_TYPE: + booleanDump(dict); + break; + case blet::Dict::NUMBER_TYPE: + numberDump(dict); + break; + case blet::Dict::STRING_TYPE: + stringDump(dict); + break; + case blet::Dict::ARRAY_TYPE: + confDumpArray("", dict); + break; + case blet::Dict::OBJECT_TYPE: + confDumpObject(dict); + break; } - info.os << "[]"; } - else { - for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { - if (i > 0) { - info.os << '\n'; - } + + inline void confDumpArray(const std::string& key, const blet::Dict& dict) { + if (dict.getValue().getArray().empty()) { // key - s_indentDump(info); - if (s_forceKeyString(key)) { - info.os << '"'; - s_stringEscape(info.os, key); - info.os << '"'; + indentDump(); + if (forceKeyString(key)) { + os_ << '"'; + stringEscape(key); + os_ << '"'; } else { - s_stringEscape(info.os, key); + stringEscape(key); } - // array operator - info.os << "[]"; // value operator - if (info.indent > 0) { - info.os << ' '; + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; + os_ << "[]"; + } + else { + for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { + if (i > 0) { + os_ << '\n'; + } + // key + indentDump(); + if (forceKeyString(key)) { + os_ << '"'; + stringEscape(key); + os_ << '"'; + } + else { + stringEscape(key); + } + // array operator + os_ << "[]"; + // value operator + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + // value + switch (dict.getValue().getArray()[i].getType()) { + case blet::Dict::NULL_TYPE: + case blet::Dict::BOOLEAN_TYPE: + case blet::Dict::NUMBER_TYPE: + case blet::Dict::STRING_TYPE: + confDumpType(dict.getValue().getArray()[i]); + break; + case blet::Dict::ARRAY_TYPE: + jsonDumpArray(dict.getValue().getArray()[i]); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObject(dict.getValue().getArray()[i]); + break; + } } - // value - switch (dict.getValue().getArray()[i].getType()) { + } + } + + inline void confDumpObject(const blet::Dict& dict) { + int index = 0; + ++sectionIndex_; + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + switch (cit->second.getType()) { case blet::Dict::NULL_TYPE: case blet::Dict::BOOLEAN_TYPE: case blet::Dict::NUMBER_TYPE: case blet::Dict::STRING_TYPE: - s_confDumpType(info, dict.getValue().getArray()[i]); + if (index > 0) { + os_ << '\n'; + } + ++index; + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + confDumpType(cit->second); break; case blet::Dict::ARRAY_TYPE: - s_jsonDumpArray(info, dict.getValue().getArray()[i]); + if (index > 0) { + os_ << '\n'; + } + ++index; + confDumpArray(cit->first, cit->second); break; case blet::Dict::OBJECT_TYPE: - s_jsonDumpObject(info, dict.getValue().getArray()[i]); break; } } - } -} - -static inline void s_confDumpObject(ConfDumpInfo& info, const blet::Dict& dict) { - int index = 0; - ++info.indexSection; - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - switch (cit->second.getType()) { - case blet::Dict::NULL_TYPE: - case blet::Dict::BOOLEAN_TYPE: - case blet::Dict::NUMBER_TYPE: - case blet::Dict::STRING_TYPE: - if (index > 0) { - info.os << '\n'; - } - ++index; - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - s_confDumpType(info, cit->second); - break; - case blet::Dict::ARRAY_TYPE: + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + if (cit->second.getType() == blet::Dict::OBJECT_TYPE) { if (index > 0) { - info.os << '\n'; + os_ << '\n'; } ++index; - s_confDumpArray(info, cit->first, cit->second); - break; - case blet::Dict::OBJECT_TYPE: - break; - } - } - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - if (cit->second.getType() == blet::Dict::OBJECT_TYPE) { - if (index > 0) { - info.os << '\n'; - } - ++index; - if (cit->second.getValue().getObject().empty()) { - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - info.os << "{}"; - } - else { - // new section - info.os << std::string(info.indexSection, '['); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; + if (cit->second.getValue().getObject().empty()) { + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + os_ << "{}"; } else { - s_stringEscape(info.os, cit->first); + // new section + os_ << std::string(sectionIndex_, '['); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + os_ << std::string(sectionIndex_, ']'); + os_ << '\n'; + confDumpType(cit->second); } - info.os << std::string(info.indexSection, ']'); - info.os << '\n'; - s_confDumpType(info, cit->second); } } + --sectionIndex_; } - --info.indexSection; -} -static inline void s_confDumpType(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - s_confDumpArray(info, "", dict); - break; - case blet::Dict::OBJECT_TYPE: - s_confDumpObject(info, dict); - break; - } -} + private: + std::size_t sectionIndex_; +}; + +/** + * @brief structure of info dumper + */ +struct ConfDumpInfo { + inline ConfDumpInfo(std::ostream& os_, std::size_t& indent_, char& indentCharacter_, enum EDumpStyle& style_) : + os(os_), + indent(indent_), + indentCharacter(indentCharacter_), + style(style_), + indexSection(0), + index(0) {} + inline ~ConfDumpInfo() {} + + std::ostream& os; + std::size_t& indent; + char& indentCharacter; + enum EDumpStyle& style; + std::size_t indexSection; + std::size_t index; +}; inline void dump(const blet::Dict& dict, std::ostream& os, std::size_t indent, char indentCharacter, enum EDumpStyle style) { os << std::setprecision(std::numeric_limits::digits10 + 1); - ConfDumpInfo info(os, indent, indentCharacter, style); - switch (info.style) { - case CONF_STYLE: - s_confDumpType(info, dict); + switch (style) { + case CONF_STYLE: { + ConfDumper conf(os, indent, indentCharacter); + conf.confDumpType(dict); break; - case JSON_STYLE: - s_jsonDumpTypeFirst(info, dict); + } + case JSON_STYLE: { + JsonDumper conf(os, indent, indentCharacter); + conf.jsonDumpTypeFirst(dict); break; + } } } @@ -6645,897 +6629,881 @@ inline std::string dump(const blet::Dict& dict, std::size_t indent, char indentC return oss.str(); } +} // namespace conf + +} // namespace blet + +// ---------------- +// End src/dump.cpp +// ---------------- + +// ----------------------- +// Start src/exception.cpp +// ----------------------- /** - * @brief structure of info parser + * exception.cpp + * + * Licensed under the MIT License . + * Copyright (c) 2024 BLET Mickaël. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ -struct ConfParseInfo { - inline ConfParseInfo(const std::string& filename_) : - filename(filename_) {} - inline ~ConfParseInfo() {} - inline std::size_t line(std::size_t i) const { - return indexToLine[i]; - } +// #include "blet/conf.h" (already included) - inline std::size_t column(std::size_t i) const { - return i - lineToIndex[indexToLine[i] - 1] + 1; - } +namespace blet { - inline std::size_t lastLine(std::size_t /*i*/) const { - return indexToLine.back(); +namespace conf { + +inline LoadException::LoadException(const std::string& filename, const std::string& message) : + std::exception(), + filename_(filename), + message_(message), + line_(0), + column_(0) { + std::ostringstream oss(""); + oss << "Load "; + if (!filename_.empty()) { + oss << filename_ << ": "; } + oss << '(' << message_ << ")"; + what_ = oss.str(); +} - inline std::size_t lastColumn(std::size_t i) const { - return i + 1 - lineToIndex[indexToLine.back() - 1]; +inline LoadException::LoadException(const std::string& filename, std::size_t line, std::size_t column, + const std::string& message) : + std::exception(), + filename_(filename), + message_(message), + line_(line), + column_(column) { + std::ostringstream oss(""); + oss << "Load at "; + if (!filename_.empty()) { + oss << filename_ << ':'; } + oss << line_ << ':' << column_ << " (" << message_ << ")"; + what_ = oss.str(); +} - const std::string& filename; - std::vector indexToLine; - std::vector lineToIndex; - std::list currentSections; -}; +inline LoadException::~LoadException() throw() {} -static inline std::string s_replaceEscapeChar(const std::string& str) { - std::string ret(str); - for (std::size_t i = 0; i < ret.size(); ++i) { - if (ret[i] == '\\') { - switch (ret[i + 1]) { - case 'a': - ret.erase(i, 1); - ret[i] = '\a'; - break; - case 'b': - ret.erase(i, 1); - ret[i] = '\b'; - break; - case 'f': - ret.erase(i, 1); - ret[i] = '\f'; - break; - case 'n': - ret.erase(i, 1); - ret[i] = '\n'; - break; - case 'r': - ret.erase(i, 1); - ret[i] = '\r'; - break; - case 't': - ret.erase(i, 1); - ret[i] = '\t'; - break; - case 'v': - ret.erase(i, 1); - ret[i] = '\v'; - break; - case '\'': - ret.erase(i, 1); - ret[i] = '\''; - break; - case '"': - ret.erase(i, 1); - ret[i] = '\"'; - break; - case '\\': - ret.erase(i, 1); - ret[i] = '\\'; - break; - } - } - } - return ret; +inline const char* LoadException::what() const throw() { + return what_.c_str(); } -static inline void s_stringJumpSpace(const std::string& str, std::size_t& index) { - // isspace - while ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ') { - ++index; - } +inline const std::string& LoadException::filename() const throw() { + return filename_; } -static inline void s_stringJumpSpaceNotNewLine(const std::string& str, std::size_t& index) { - // isspace - while (str[index] != '\n' && ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ')) { - ++index; - } +inline const std::string& LoadException::message() const throw() { + return message_; } -static inline void s_stringReverseJumpSpace(const std::string& str, std::size_t& index) { - // isspace - while (index > 0 && ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ')) { - --index; - } +inline const std::size_t& LoadException::line() const throw() { + return line_; } -void s_parseType(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict); -void s_parseJsonArray(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict); +inline const std::size_t& LoadException::column() const throw() { + return column_; +} -static inline bool s_hex(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; +} // namespace conf - if (value[index] == '-' || value[index] == '+') { - ++index; - } - // is hex - if (value[index] == '0' && (value[index + 1] == 'x' || value[index + 1] == 'X')) { - ++index; - ++index; - while (value[index] != '\0') { - if (value[index] >= '0' && value[index] <= '9') { - ++index; - } - else if (value[index] >= 'a' && value[index] <= 'f') { - ++index; - } - else { - return false; - } - } - stringStream << ::strtol(value.c_str(), NULL, 16); - return true; - } - else { - return false; +} // namespace blet + +// --------------------- +// End src/exception.cpp +// --------------------- + +// ------------------ +// Start src/load.cpp +// ------------------ +/** + * load.cpp + * + * Licensed under the MIT License . + * Copyright (c) 2024 BLET Mickaël. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include // std::ifstream + +// #include "blet/conf.h" (already included) + +namespace blet { + +namespace conf { + +class StringReader { + public: + inline StringReader(std::istream& stream) : + stream_(stream), + pos_(0), + line_(1), + posColumn_(0), + nl_('\n') { + stream_.seekg(0, stream_.end); + std::size_t streamLength = stream_.tellg(); + stream_.seekg(0, stream_.beg); + std::vector vBuffer(streamLength); + stream_.read(vBuffer.data(), vBuffer.size()); + str_.append(vBuffer.begin(), vBuffer.end()); + } + + inline ~StringReader() {} + + template + inline bool operator==(const char (&str)[Size]) { + return !str_.compare(pos_, Size - 1, str, Size - 1); } -} -static inline bool s_binary(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; - bool sub = false; - if (value[index] == '-' || value[index] == '+') { - if (value[index] == '-') { - sub = true; - } - ++index; - } - // is binary - if (value[index] == '0' && value[index + 1] == 'b') { - ++index; - ++index; - std::size_t start = index; - while (value[index] != '\0') { - if (value[index] == '0' || value[index] == '1') { - ++index; - } - else { - return false; - } - } - if (sub) { - long lvalue; - std::stringstream ss(""); - ss << '-' << ::strtoul(value.c_str() + start, NULL, 2); - ss >> lvalue; - stringStream << lvalue; - } - else { - stringStream << ::strtoul(value.c_str() + start, NULL, 2); + inline void operator++() { + if (str_[pos_] == '\n') { + posColumn_ = pos_ + 1; + ++line_; } - return true; + ++pos_; } - else { - return false; + + inline void operator+=(std::size_t index) { + for (std::size_t i = 0; i < index; ++i) { + operator++(); + } } -} -static inline bool s_octal(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; + inline const char& operator[](std::size_t index) { + return str_[pos_ + index]; + } - if (value[index] == '-' || value[index] == '+') { - ++index; + inline std::string substr(std::size_t start, std::size_t end) { + return str_.substr(start, end - start); } - // is binary - if (value[index] == '0' && value.find('.') == std::string::npos && value.find('e') == std::string::npos) { - while (value[index] != '\0') { - if (value[index] >= '0' && value[index] <= '8') { - ++index; - } - else { - return false; - } - } - stringStream << ::strtol(value.c_str(), NULL, 8); - return true; + + inline std::size_t line() { + return line_; } - else { - return false; + + inline std::size_t column() { + return pos_ - posColumn_ + 1; } -} -static inline bool s_double(const std::string& value, std::ostream& stringStream) { - char* endPtr = NULL; - stringStream << ::strtod(value.c_str(), &endPtr); - if (endPtr != NULL && endPtr[0] != '\0') { - return false; + inline std::size_t index() { + return pos_; } - return true; -} -static inline void s_parseNumber(const std::string& str, blet::Dict& dict) { - std::stringstream stringStream(""); - if (s_hex(str, stringStream) || s_binary(str, stringStream) || s_octal(str, stringStream) || - s_double(str, stringStream)) { - double num; - stringStream >> num; - dict.newNumber(num); + inline std::istream& streamOffset(std::size_t offset) { + return stream_.seekg(offset, stream_.beg); } - else { - dict = str; + + private: + std::istream& stream_; + std::string str_; + std::size_t pos_; + std::string sLine_; + std::size_t line_; + std::size_t posColumn_; + const char nl_; +}; + +class Loader { + public: + inline Loader(const std::string& filename, std::istream& stream) : + filename_(filename), + reader_(stream) {} + + inline ~Loader() {} + + enum EValueFromType { + DEFAULT_VALUE_FROM_TYPE, + OBJECT_VALUE_FROM_TYPE, + ARRAY_VALUE_FROM_TYPE + }; + + inline void load(blet::Dict& dict) { + spaceJump(); + while (reader_[0] != '\0') { + loadType(dict); + } } -} -static inline void s_parseValue(const std::string& str, blet::Dict& dict) { - std::string lowerStr(str); - for (std::size_t i = 0; i < lowerStr.size(); ++i) { - if (lowerStr[i] >= 'A' && lowerStr[i] <= 'Z') { - lowerStr[i] = lowerStr[i] + ' '; - } - } - switch (lowerStr[0]) { - case '-': - case '+': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - s_parseNumber(str, dict); - break; - case 'f': - if (lowerStr == "false") { - dict = false; + inline void loadType(blet::Dict& dict) { + if (reader_[0] == '[') { + loadSection(dict); + } + else if (reader_[0] == '{') { + if (currentSections_.empty()) { + loadObject(dict); } else { - dict = str; - } - break; - case 'n': - if (lowerStr == "no") { - dict = false; + loadObject(*(currentSections_.top())); } - else if (lowerStr == "null") { - dict.newNull(); + } + else { + // create the default section if needed + if (currentSections_.empty()) { + currentSections_.push(&dict); } - else { - dict = str; + loadKey(*(currentSections_.top())); + } + } + + inline void loadSection(blet::Dict& dict, bool linear = false) { + ++reader_; // jump '[' + spaceJumpLine(); + // multi section + if (reader_[0] == '[') { + std::size_t level = 1; + while (reader_[0] == '[') { + ++reader_; // jump '[' + ++level; + spaceJumpLine(); } - break; - case 'o': - if (lowerStr == "on") { - dict = true; + std::string sectionName = loadSectionName(); + std::size_t maxlevel = level - 1; + while (reader_[0] == ']') { + ++reader_; // jump ']' + --level; + spaceJumpLine(); } - else if (lowerStr == "off") { - dict = false; + if (level != 0) { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); } - else { - dict = str; + while (currentSections_.size() > maxlevel) { + currentSections_.pop(); } - break; - case 't': - if (lowerStr == "true") { - dict = true; + if (maxlevel == currentSections_.size()) { + currentSections_.push(&(currentSections_.top()->operator[](sectionName))); } else { - dict = str; + throw LoadException(filename_, reader_.line(), reader_.column(), "Section without parent"); } - break; - case 'y': - if (lowerStr == "yes") { - dict = true; + } + // basic or linear section + else { + if (!linear) { + // clear currentSections_ + currentSections_ = std::stack(); + } + std::string sectionName = loadSectionName(); + if (sectionName.empty()) { + // set the default section + currentSections_.push(&dict); } else { - dict = str; + // add new section + currentSections_.push(&(dict[sectionName])); } - break; - default: - dict = str; - break; - } - if (dict.isString()) { - dict = s_replaceEscapeChar(dict.getValue().getString()); + ++reader_; // jump ']' + } + spaceJumpLine(); + if (reader_[0] == '[') { + // recursive linear + loadSection(*(currentSections_.top()), true); + } + spaceJump(); } -} -static inline void s_parseJsonObject(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - blet::Dict* currentDict; - ConfParseInfo valueInfo = info; - std::size_t start; - std::size_t end; - bool next = false; - - dict.newObject(); - ++i; // jump '{' - s_stringJumpSpace(str, i); - while (str[i] != '}' || next) { - if (str[i] == '\0') { - throw LoadException(info.filename, info.lastLine(i), info.lastColumn(i), "End of json object value"); - } - // parse key - if (str[i] == '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Key of json object value"); - } - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote at key in json object value"); - } - ++i; + inline std::string loadSectionName() { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + // section name + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != ']') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); } - end = i; - ++i; // jump quote + return stringEscape(reader_.substr(start, end)); } else { - start = i; - while (str[i] != '=') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key in json object value"); + while (reader_[0] != ']') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - currentDict = &(dict.operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - valueInfo.currentSections.push_back(currentDict); - s_stringJumpSpace(str, i); - if (str[i] != '=') { - throw LoadException(info.filename, info.line(i), info.column(i), - "Operator = not found in json object value"); - } - ++i; // jump '=' - s_stringJumpSpace(str, i); - // start value - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote at value in json object value"); + else { + ++reader_; + end = reader_.index(); } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpace(str, i); - *currentDict = s_replaceEscapeChar(str.substr(start, end - start)); - } - else { - switch (str[i]) { - case '{': - s_parseJsonObject(str, valueInfo, i, *currentDict); - break; - case '[': - s_parseJsonArray(str, valueInfo, i, *currentDict); - break; - default: - start = i; - while (str[i] != ',' && str[i] != '}' && str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), *currentDict); - break; } + return reader_.substr(start, end); } - s_stringJumpSpace(str, i); - if (str[i] == ',') { - ++i; // jump ',' - s_stringJumpSpace(str, i); - next = true; + } + + inline void loadKey(blet::Dict& dict) { + if (reader_[0] == '=' || reader_[0] == ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Key not found"); } - else { - next = false; + blet::Dict* pKeyDict = loadKeyDict(dict); + spaceJump(); + if (reader_[0] != '=' && reader_[0] != ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); } + ++reader_; // jump '=' or ':' + spaceJumpLine(); + loadValue(*pKeyDict); + spaceJump(); } - ++i; // jump '}' - s_stringJumpSpace(str, i); -} -inline void s_parseJsonArray(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - std::size_t start; - std::size_t end; - bool next = false; - - dict.newArray(); - ++i; // jump '[' - s_stringJumpSpace(str, i); - // parse value array - while (str[i] != ']' || next) { - if (str[i] == '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of json array value"); - } - // start value - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote in json array value"); - } - ++i; + inline blet::Dict* loadKeyDict(blet::Dict& dict) { + blet::Dict* pCurrentDict = &(dict[loadKeyName()]); + // map key + while (reader_[0] == '[') { + std::string keyMapName = loadKeyMapName(); + if (keyMapName.empty()) { + pCurrentDict->push_back(blet::Dict()); + pCurrentDict = &(pCurrentDict->back()); } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ',' && str[i] != ']' && str[i] != '\n' && str[i] != '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of value in json array value"); + else { + pCurrentDict = &(pCurrentDict->operator[](keyMapName)); } - dict.push_back(s_replaceEscapeChar(str.substr(start, end - start))); } - else { - dict.push_back(blet::Dict()); - info.currentSections.push_back(&(dict.back())); - switch (str[i]) { - case '{': - s_parseJsonObject(str, info, i, dict.back()); - break; - case '[': - s_parseJsonArray(str, info, i, dict.back()); - break; - default: - start = i; - while (str[i] != ',' && str[i] != ']' && str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), dict.back()); + return pCurrentDict; + } + + inline std::string loadKeyName() { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != '=' && reader_[0] != ':' && reader_[0] != '[') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); } } - s_stringJumpSpace(str, i); - if (str[i] == ',') { - ++i; // jump ',' - s_stringJumpSpace(str, i); - next = true; - } else { - next = false; + while (reader_[0] != '=' && reader_[0] != ':' && reader_[0] != '[') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } } + return stringEscape(reader_.substr(start, end)); } - ++i; // jump ']' - s_stringJumpSpace(str, i); -} -static inline void s_parseSection(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - std::size_t level = 1; - std::size_t start; - std::size_t end; - ++i; // jump '[' - s_stringJumpSpaceNotNewLine(str, i); - // is multi section level - if (str[i] == '[') { - while (str[i] == '[') { - ++i; // jump '[' - ++level; - s_stringJumpSpaceNotNewLine(str, i); - } - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote multi section"); - } - ++i; + inline std::string loadKeyMapName() { + ++reader_; // jump '[' + spaceJumpLine(); + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != ']') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of map"); } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); } else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of multi section"); + while (reader_[0] != ']') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of map"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); } - ++i; } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - std::size_t maxlevel = level; - while (str[i] == ']') { - ++i; // jump ']' - --level; - s_stringJumpSpaceNotNewLine(str, i); } - if (level != 0) { - throw LoadException(info.filename, info.line(i), info.column(i), "End of multi section"); - } - --maxlevel; - while (info.currentSections.size() > maxlevel) { - info.currentSections.pop_back(); - } - if (maxlevel == info.currentSections.size()) { - info.currentSections.push_back( - &(info.currentSections.back()->operator[](s_replaceEscapeChar(str.substr(start, end - start))))); - } - else { - throw LoadException(info.filename, info.line(i), info.column(i), "Multi section without parent"); - } - s_stringJumpSpaceNotNewLine(str, i); - } - else { - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote section"); + ++reader_; // jump ']' + spaceJumpLine(); + return stringEscape(reader_.substr(start, end)); + } + + inline void loadValue(blet::Dict& dict, EValueFromType fromType = DEFAULT_VALUE_FROM_TYPE) { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + switch (reader_[0]) { + case '"': + case '\'': { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + commentJump(); + if (reader_[0] != '\n' && reader_[0] != '\0') { + if (fromType == DEFAULT_VALUE_FROM_TYPE || + (fromType == OBJECT_VALUE_FROM_TYPE && reader_[0] != '}' && reader_[0] != ',') || + (fromType == ARRAY_VALUE_FROM_TYPE && reader_[0] != ']' && reader_[0] != ',')) { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of value"); + } } - ++i; + dict.clear(); + dict = stringEscape(reader_.substr(start, end)); + break; + } + case '{': { + loadObject(dict); + break; } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of section"); + case '[': { + loadArray(dict); + break; } - ++i; // jump ']' - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of section"); + default: { + while (reader_[0] != '\n' && reader_[0] != ';' && reader_[0] != '#' && reader_[0] != '\0') { + if (fromType == OBJECT_VALUE_FROM_TYPE && (reader_[0] == '}' || reader_[0] == ',')) { + break; + } + if (fromType == ARRAY_VALUE_FROM_TYPE && (reader_[0] == ']' || reader_[0] == ',')) { + break; + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } } - ++i; + dict.clear(); + getValue(dict, reader_.substr(start, end)); + break; } - end = i - 1; - ++i; // jump ']' - s_stringReverseJumpSpace(str, end); - ++end; } - info.currentSections.clear(); - if (end - start > 0) { - info.currentSections.push_back(&(dict[s_replaceEscapeChar(str.substr(start, end - start))])); + } + + inline void loadObject(blet::Dict& dict) { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + bool next = false; + + if (!dict.isObject()) { + dict.newObject(); } - else { - info.currentSections.push_back(&dict); - } - s_stringJumpSpaceNotNewLine(str, i); - while (str[i] == '[') { - ++i; - s_stringJumpSpaceNotNewLine(str, i); - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; + ++reader_; // jump '{' + spaceJump(); + while (reader_[0] != '}' || next) { + if (reader_[0] == '\0') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of object"); + } + if (reader_[0] == '=' || reader_[0] == ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Key not found"); + } + // start key name + if (reader_[0] == '\"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + } + else { + start = reader_.index(); + while (reader_[0] != '=' && reader_[0] != ':') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of key"); } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote linear section"); + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of linear section"); } - ++i; // jump ']' + } + blet::Dict* objDict = &(dict.operator[](stringEscape(reader_.substr(start, end)))); + spaceJump(); + if (reader_[0] != '=' && reader_[0] != ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + ++reader_; // jump '=' or ':' + spaceJump(); + // recursive + loadValue(*objDict, OBJECT_VALUE_FROM_TYPE); + spaceJump(); + if (reader_[0] == ',') { + ++reader_; // jump ',' + spaceJump(); + next = true; } else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of linear section"); - } - ++i; - } - end = i - 1; - ++i; // jump ']' - s_stringReverseJumpSpace(str, end); - ++end; + next = false; } - info.currentSections.push_back( - &(info.currentSections.back()->operator[](s_replaceEscapeChar(str.substr(start, end - start))))); - s_stringJumpSpaceNotNewLine(str, i); } + ++reader_; // jump '}' + spaceJump(); } - s_stringJumpSpace(str, i); -} -inline void s_parseKeyValue(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - blet::Dict* currentDict = &dict; - ConfParseInfo valueInfo = info; - if (str[i] == '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Key not found"); - } - std::size_t start; - std::size_t end; - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; + inline void loadArray(blet::Dict& dict) { + bool next = false; + + dict.newArray(); + ++reader_; // jump '[]' + spaceJump(); + while (reader_[0] != ']' || next) { + if (reader_[0] == '\0') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of array"); } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); + dict.push_back(blet::Dict()); + blet::Dict& arrDict = dict.back(); + // recursive + loadValue(arrDict, ARRAY_VALUE_FROM_TYPE); + spaceJump(); + if (reader_[0] == ',') { + ++reader_; // jump ',' + spaceJump(); + next = true; + } + else { + next = false; } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '=' && str[i] != '[') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key"); } + ++reader_; // jump '}' + spaceJump(); } - else { - start = i; - while (str[i] != '=' && str[i] != '[') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key"); + + inline void getValue(blet::Dict& dict, const std::string& value) { + std::string lowerStr(value); + for (std::size_t i = 0; i < lowerStr.size(); ++i) { + if (lowerStr[i] >= 'A' && lowerStr[i] <= 'Z') { + lowerStr[i] = lowerStr[i] + ' '; } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - currentDict = &(currentDict->operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - valueInfo.currentSections.push_back(currentDict); - // check table key - while (str[i] == '[') { - ++i; // jump '[' - s_stringJumpSpaceNotNewLine(str, i); - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; + } + switch (lowerStr[0]) { + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + s_parseNumber(value, dict); + break; + case 'f': + if (lowerStr == "false") { + dict = false; } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); + else { + dict = value; } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key of map"); - } - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of map"); + break; + case 'n': + if (lowerStr == "no") { + dict = false; } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - ++i; // jump ']' - s_stringJumpSpaceNotNewLine(str, i); - if (end - start > 0) { - currentDict = &(currentDict->operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - } - else { - currentDict->push_back(blet::Dict()); - currentDict = &(currentDict->back()); + else if (lowerStr == "none" || lowerStr == "null") { + dict.newNull(); + } + else { + dict = value; + } + break; + case 'o': + if (lowerStr == "on") { + dict = true; + } + else if (lowerStr == "off") { + dict = false; + } + else { + dict = value; + } + break; + case 't': + if (lowerStr == "true") { + dict = true; + } + else { + dict = value; + } + break; + case 'y': + if (lowerStr == "yes") { + dict = true; + } + else { + dict = value; + } + break; + case '\0': + dict.newNull(); + break; + default: + dict = value; + break; } - valueInfo.currentSections.push_back(currentDict); - } - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Operator = not found"); } - ++i; // jump '=' - s_stringJumpSpaceNotNewLine(str, i); - // start value - if (str[i] == '\"' || str[i] == '\'') { + + inline void loadQuoteIndexes(std::size_t* pStart, std::size_t* pEnd) { // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; + const char quote = reader_[0]; + ++reader_; // jump quote + *pStart = reader_.index(); // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; + while (reader_[0] != quote) { + if (reader_[0] == '\\' && (reader_[1] == quote || reader_[1] == '\\')) { + ++reader_; } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of quote"); } - ++i; + ++reader_; } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '\n' && str[i] != '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of value"); - } - *currentDict = s_replaceEscapeChar(str.substr(start, end - start)); + *pEnd = reader_.index(); + ++reader_; // jump quote } - else { - switch (str[i]) { - case '{': - s_parseJsonObject(str, valueInfo, i, *currentDict); - break; - case '[': - s_parseJsonArray(str, valueInfo, i, *currentDict); - break; - default: - start = i; - while (str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), *currentDict); - break; + + inline void spaceJumpLine() { + // isspace + while (reader_[0] != '\n' && ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ')) { + ++reader_; } } - s_stringJumpSpace(str, i); -} -inline void s_parseType(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - if (str[i] == '[') { - s_parseSection(str, info, i, dict); + inline void spaceJump() { + commentJump(); + // isspace + while ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + ++reader_; + commentJump(); + } } - else { - if (info.currentSections.empty()) { - info.currentSections.push_back(&dict); + + inline void commentJump() { + if (reader_[0] == '#' || reader_[0] == ';') { + while (reader_[0] != '\0' && reader_[0] != '\n') { + ++reader_; // jump character + } } - s_parseKeyValue(str, info, i, *(info.currentSections.back())); } -} -static inline void s_replaceCommentBySpace(std::string& str) { - // replace double backslash - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - if (str[i] == '\\' && str[i + 1] == '\\') { - str[i] = replaceBackslash; - str[i + 1] = replaceBackslash; - } - } - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - // jump inside of string - if (str[i] == '"' || str[i] == '\'') { - const char quote = str[i]; - ++i; - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; // escape character - } - if (str[i] == '\n' || str[i] == '\0') { - break; + static inline std::string stringEscape(const std::string& str) { + std::ostringstream oss(""); + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == '\\') { + switch (str[i + 1]) { + case 'a': + oss << '\a'; + break; + case 'b': + oss << '\b'; + break; + case 'f': + oss << '\f'; + break; + case 'n': + oss << '\n'; + break; + case 'r': + oss << '\r'; + break; + case 't': + oss << '\t'; + break; + case 'v': + oss << '\v'; + break; + case '\'': + oss << '\''; + break; + case '"': + oss << '\"'; + break; + case '\\': + oss << '\\'; + break; + default: + oss << '\\'; + if (str[i + 1] != '\0') { + oss << str[i + 1]; + } + break; } ++i; } + else { + oss << str[i]; + } } - // replace - else if ((str[i] == '#' || str[i] == ';') && (i == 0 || (i > 0 && str[i - 1] != '\\'))) { - while (str[i] != '\0' && str[i] != '\n') { - str[i] = ' '; - ++i; + return oss.str(); + } + + private: + static inline bool s_hex(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + + if (value[index] == '-' || value[index] == '+') { + ++index; + } + // is hex + if (value[index] == '0' && (value[index + 1] == 'x' || value[index + 1] == 'X')) { + ++index; + ++index; + while (value[index] != '\0') { + if (value[index] >= '0' && value[index] <= '9') { + ++index; + } + else if (value[index] >= 'a' && value[index] <= 'f') { + ++index; + } + else { + return false; + } } + stringStream << ::strtol(value.c_str(), NULL, 16); + return true; + } + else { + return false; } } - // replace double backslash - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - if (str[i] == replaceBackslash && str[i + 1] == replaceBackslash) { - str[i] = '\\'; - str[i + 1] = '\\'; + + static inline bool s_binary(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + bool sub = false; + if (value[index] == '-' || value[index] == '+') { + if (value[index] == '-') { + sub = true; + } + ++index; + } + // is binary + if (value[index] == '0' && value[index + 1] == 'b') { + ++index; + ++index; + std::size_t start = index; + while (value[index] != '\0') { + if (value[index] == '0' || value[index] == '1') { + ++index; + } + else { + return false; + } + } + if (sub) { + long lvalue; + std::stringstream ss(""); + ss << '-' << ::strtoul(value.c_str() + start, NULL, 2); + ss >> lvalue; + stringStream << lvalue; + } + else { + stringStream << ::strtoul(value.c_str() + start, NULL, 2); + } + return true; + } + else { + return false; } } -} -static inline std::string s_streamToStr(ConfParseInfo& info, std::istream& stream) { - // generate line and column index - std::ostringstream oss(""); - std::string line(""); - std::size_t nLine = 1; - info.lineToIndex.push_back(0); - while (std::getline(stream, line)) { - if (nLine > 1) { - oss << '\n'; - } - oss << line; - info.lineToIndex.push_back(line.size() + 1 + info.lineToIndex.back()); - info.indexToLine.insert(info.indexToLine.end(), line.size() + 1, nLine); - ++nLine; + static inline bool s_octal(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + + if (value[index] == '-' || value[index] == '+') { + ++index; + } + // is binary + if (value[index] == '0' && value.find('.') == std::string::npos && value.find('e') == std::string::npos) { + while (value[index] != '\0') { + if (value[index] >= '0' && value[index] <= '8') { + ++index; + } + else { + return false; + } + } + stringStream << ::strtol(value.c_str(), NULL, 8); + return true; + } + else { + return false; + } } - return oss.str(); -} -static inline blet::Dict s_loadStream(std::istream& stream, const std::string& filename) { - ConfParseInfo info(filename); - std::string str = s_streamToStr(info, stream); - s_replaceCommentBySpace(str); - std::size_t i = 0; - s_stringJumpSpace(str, i); - try { - blet::Dict dict; - while (str[i] != '\0') { - s_parseType(str, info, i, dict); + static inline bool s_double(const std::string& value, std::ostream& stringStream) { + char* endPtr = NULL; + stringStream << ::strtod(value.c_str(), &endPtr); + if (endPtr != NULL && endPtr[0] != '\0') { + return false; } - return dict; + return true; } - catch (const LoadException& /*e*/) { - throw; + + static inline void s_parseNumber(const std::string& str, blet::Dict& dict) { + std::stringstream stringStream(""); + if (s_hex(str, stringStream) || s_binary(str, stringStream) || s_octal(str, stringStream) || + s_double(str, stringStream)) { + double num; + stringStream >> num; + dict.newNumber(num); + } + else { + dict = str; + } } -} + + const std::string filename_; + StringReader reader_; + std::stack currentSections_; +}; inline blet::Dict loadFile(const char* filename) { std::ifstream fileStream(filename); // open file if (!fileStream.is_open()) { throw LoadException(filename, "Open file failed"); } - return s_loadStream(fileStream, filename); + blet::Dict dict; + Loader loader(filename, fileStream); + loader.load(dict); + return dict; } inline blet::Dict loadStream(std::istream& stream) { - return s_loadStream(stream, std::string()); + blet::Dict dict; + Loader loader("", stream); + loader.load(dict); + return dict; } inline blet::Dict loadString(const std::string& str) { @@ -7552,7 +7520,7 @@ inline blet::Dict loadData(const void* data, std::size_t size) { } // namespace blet // ---------------- -// End src/conf.cpp +// End src/load.cpp // ---------------- -#endif // #ifndef AMALGAMATE_GUARD__SINGLE_INCLUDE_BLET_CONF_H_ \ No newline at end of file +#endif // #ifndef AMALGAMATE_GUARD__SINGLE_INCLUDE_BLET_CONF_H_ diff --git a/src/conf.cpp b/src/conf.cpp deleted file mode 100644 index 4953e91..0000000 --- a/src/conf.cpp +++ /dev/null @@ -1,1493 +0,0 @@ -/** - * conf.cpp - * - * Licensed under the MIT License . - * Copyright (c) 2022-2023 BLET Mickaël. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "blet/conf.h" - -#include -#include -#include - -#include // std::ifstream -#include // std::setprecision -#include // std::numeric_limits - -namespace blet { - -namespace conf { - -const char replaceBackslash = -42; - -LoadException::LoadException(const std::string& filename, const std::string& message) : - std::exception(), - filename_(filename), - message_(message), - line_(0), - column_(0) { - std::ostringstream oss(""); - oss << "Parse "; - if (!filename_.empty()) { - oss << filename_ << ": "; - } - oss << '(' << message_ << ")"; - what_ = oss.str(); -} - -LoadException::LoadException(const std::string& filename, std::size_t line, std::size_t column, - const std::string& message) : - std::exception(), - filename_(filename), - message_(message), - line_(line), - column_(column) { - std::ostringstream oss(""); - oss << "Parse at "; - if (!filename_.empty()) { - oss << filename_ << ':'; - } - oss << line_ << ':' << column_ << " (" << message_ << ")"; - what_ = oss.str(); -} - -LoadException::~LoadException() throw() {} - -const char* LoadException::what() const throw() { - return what_.c_str(); -} - -const std::string& LoadException::filename() const throw() { - return filename_; -} - -const std::string& LoadException::message() const throw() { - return message_; -} - -const std::size_t& LoadException::line() const throw() { - return line_; -} - -const std::size_t& LoadException::column() const throw() { - return column_; -} - -/** - * @brief structure of info dumper - */ -struct ConfDumpInfo { - ConfDumpInfo(std::ostream& os_, std::size_t& indent_, char& indentCharacter_, enum EDumpStyle& style_) : - os(os_), - indent(indent_), - indentCharacter(indentCharacter_), - style(style_), - indexSection(0), - index(0) {} - ~ConfDumpInfo() {} - - std::ostream& os; - std::size_t& indent; - char& indentCharacter; - enum EDumpStyle& style; - std::size_t indexSection; - std::size_t index; -}; - -// ----------------------------------------------------------------------------- -// ................................. -// .#####...##..##..##...##..#####.. -// .##..##..##..##..###.###..##..##. -// .##..##..##..##..##.#.##..#####.. -// .##..##..##..##..##...##..##..... -// .#####....####...##...##..##..... -// ................................. -// ----------------------------------------------------------------------------- - -static inline bool s_forceKeyString(const std::string& str) { - bool ret = false; - if (str.empty()) { - ret = true; - } - else { - for (std::size_t i = 0; i < str.size(); ++i) { - if (str[i] == ' ' || str[i] == '=' || str[i] == ',' || str[i] == ';' || str[i] == '#' || str[i] == '[' || - str[i] == ']' || str[i] == '"' || str[i] == '\'' || str[i] == '\a' || str[i] == '\b' || - str[i] == '\f' || str[i] == '\r' || str[i] == '\t' || str[i] == '\v' || str[i] == '\\') { - ret = true; - break; - } - } - } - return ret; -} - -static inline void s_stringEscape(std::ostream& oss, const std::string& str) { - for (std::size_t i = 0; i < str.size(); ++i) { - switch (str[i]) { - case '\a': - oss << '\\' << 'a'; - break; - case '\b': - oss << '\\' << 'b'; - break; - case '\f': - oss << '\\' << 'f'; - break; - case '\n': - oss << '\\' << 'n'; - break; - case '\r': - oss << '\\' << 'r'; - break; - case '\t': - oss << '\\' << 't'; - break; - case '\v': - oss << '\\' << 'v'; - break; - case '\'': - oss << '\\' << '\''; - break; - case '"': - oss << '\\' << '"'; - break; - case '\\': - oss << '\\' << '\\'; - break; - default: - oss << str[i]; - break; - } - } -} - -static inline void s_newlineDump(ConfDumpInfo& info, const blet::Dict& dict) { - if (info.indent != 0) { - if (dict.getType() == blet::Dict::OBJECT_TYPE && !dict.getValue().getObject().empty()) { - info.os << '\n'; - } - if (dict.getType() == blet::Dict::ARRAY_TYPE && !dict.getValue().getArray().empty()) { - info.os << '\n'; - } - } -} - -static inline void s_indentDump(ConfDumpInfo& info) { - if (info.indent != 0) { - info.os << std::string(info.indent * info.index, info.indentCharacter); - } -} - -static inline void s_nullDump(ConfDumpInfo& info, const blet::Dict& /*dict*/) { - info.os << "null"; -} - -static inline void s_numberDump(ConfDumpInfo& info, const blet::Dict& dict) { - info.os << dict.getValue().getNumber(); -} - -static inline void s_booleanDump(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getBoolean()) { - info.os << "true"; - } - else { - info.os << "false"; - } -} - -static inline void s_stringDump(ConfDumpInfo& info, const blet::Dict& dict) { - info.os << '"'; - s_stringEscape(info.os, dict.getValue().getString()); - info.os << '"'; -} - -// ----------------------------------------------------------------------------- -// ......................................................................... -// .######...####....####...##..##..........#####...##..##..##...##..#####.. -// .....##..##......##..##..###.##..........##..##..##..##..###.###..##..##. -// .....##...####...##..##..##.###..........##..##..##..##..##.#.##..#####.. -// .##..##......##..##..##..##..##..........##..##..##..##..##...##..##..... -// ..####....####....####...##..##..........#####....####...##...##..##..... -// ......................................................................... -// ----------------------------------------------------------------------------- - -static void s_jsonDumpType(ConfDumpInfo& info, const blet::Dict& dict); - -static inline void s_jsonDumpObject(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getValue().getObject().empty()) { - info.os << "{}"; - } - else { - info.os << '{'; - s_newlineDump(info, dict); - ++info.index; - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - if (cit != dict.getValue().getObject().begin()) { - info.os << ','; - s_newlineDump(info, dict); - } - // key - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - s_jsonDumpType(info, cit->second); - } - --info.index; - s_newlineDump(info, dict); - s_indentDump(info); - info.os << '}'; - } -} - -static inline void s_jsonDumpArray(ConfDumpInfo& info, const blet::Dict& dict) { - if (dict.getValue().getArray().empty()) { - info.os << "[]"; - } - else { - info.os << '['; - s_newlineDump(info, dict); - ++info.index; - for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { - if (i > 0) { - info.os << ','; - s_newlineDump(info, dict); - } - // value - s_indentDump(info); - s_jsonDumpType(info, dict.getValue().getArray()[i]); - } - --info.index; - s_newlineDump(info, dict); - s_indentDump(info); - info.os << ']'; - } -} - -static inline void s_jsonDumpType(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - s_jsonDumpArray(info, dict); - break; - case blet::Dict::OBJECT_TYPE: - s_jsonDumpObject(info, dict); - break; - } -} - -static inline void s_jsonDumpObjectFirst(ConfDumpInfo& info, const blet::Dict& dict) { - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - if (cit != dict.getValue().getObject().begin()) { - info.os << '\n'; - } - // key - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - s_jsonDumpType(info, cit->second); - } -} - -static inline void s_jsonDumpTypeFirst(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - info.os << "\"\""; - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - s_jsonDumpArray(info, dict); - break; - case blet::Dict::OBJECT_TYPE: - s_jsonDumpObjectFirst(info, dict); - break; - } -} - -// ----------------------------------------------------------------------------- -// ......................................................................... -// ..####....####...##..##..######..........#####...##..##..##...##..#####.. -// .##..##..##..##..###.##..##..............##..##..##..##..###.###..##..##. -// .##......##..##..##.###..####............##..##..##..##..##.#.##..#####.. -// .##..##..##..##..##..##..##..............##..##..##..##..##...##..##..... -// ..####....####...##..##..##..............#####....####...##...##..##..... -// ......................................................................... -// ----------------------------------------------------------------------------- - -static void s_confDumpType(ConfDumpInfo& info, const blet::Dict& dict); - -static inline void s_confDumpArray(ConfDumpInfo& info, const std::string& key, const blet::Dict& dict) { - if (dict.getValue().getArray().empty()) { - // key - s_indentDump(info); - if (s_forceKeyString(key)) { - info.os << '"'; - s_stringEscape(info.os, key); - info.os << '"'; - } - else { - s_stringEscape(info.os, key); - } - // value operator - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - info.os << "[]"; - } - else { - for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { - if (i > 0) { - info.os << '\n'; - } - // key - s_indentDump(info); - if (s_forceKeyString(key)) { - info.os << '"'; - s_stringEscape(info.os, key); - info.os << '"'; - } - else { - s_stringEscape(info.os, key); - } - // array operator - info.os << "[]"; - // value operator - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - // value - switch (dict.getValue().getArray()[i].getType()) { - case blet::Dict::NULL_TYPE: - case blet::Dict::BOOLEAN_TYPE: - case blet::Dict::NUMBER_TYPE: - case blet::Dict::STRING_TYPE: - s_confDumpType(info, dict.getValue().getArray()[i]); - break; - case blet::Dict::ARRAY_TYPE: - s_jsonDumpArray(info, dict.getValue().getArray()[i]); - break; - case blet::Dict::OBJECT_TYPE: - s_jsonDumpObject(info, dict.getValue().getArray()[i]); - break; - } - } - } -} - -static inline void s_confDumpObject(ConfDumpInfo& info, const blet::Dict& dict) { - int index = 0; - ++info.indexSection; - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - switch (cit->second.getType()) { - case blet::Dict::NULL_TYPE: - case blet::Dict::BOOLEAN_TYPE: - case blet::Dict::NUMBER_TYPE: - case blet::Dict::STRING_TYPE: - if (index > 0) { - info.os << '\n'; - } - ++index; - s_indentDump(info); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - s_confDumpType(info, cit->second); - break; - case blet::Dict::ARRAY_TYPE: - if (index > 0) { - info.os << '\n'; - } - ++index; - s_confDumpArray(info, cit->first, cit->second); - break; - case blet::Dict::OBJECT_TYPE: - break; - } - } - for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); - cit != dict.getValue().getObject().end(); ++cit) { - if (cit->second.getType() == blet::Dict::OBJECT_TYPE) { - if (index > 0) { - info.os << '\n'; - } - ++index; - if (cit->second.getValue().getObject().empty()) { - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - if (info.indent > 0) { - info.os << ' '; - } - info.os << '='; - if (info.indent > 0) { - info.os << ' '; - } - info.os << "{}"; - } - else { - // new section - info.os << std::string(info.indexSection, '['); - if (s_forceKeyString(cit->first)) { - info.os << '"'; - s_stringEscape(info.os, cit->first); - info.os << '"'; - } - else { - s_stringEscape(info.os, cit->first); - } - info.os << std::string(info.indexSection, ']'); - info.os << '\n'; - s_confDumpType(info, cit->second); - } - } - } - --info.indexSection; -} - -static inline void s_confDumpType(ConfDumpInfo& info, const blet::Dict& dict) { - switch (dict.getType()) { - case blet::Dict::NULL_TYPE: - s_nullDump(info, dict); - break; - case blet::Dict::BOOLEAN_TYPE: - s_booleanDump(info, dict); - break; - case blet::Dict::NUMBER_TYPE: - s_numberDump(info, dict); - break; - case blet::Dict::STRING_TYPE: - s_stringDump(info, dict); - break; - case blet::Dict::ARRAY_TYPE: - s_confDumpArray(info, "", dict); - break; - case blet::Dict::OBJECT_TYPE: - s_confDumpObject(info, dict); - break; - } -} - -void dump(const blet::Dict& dict, std::ostream& os, std::size_t indent, char indentCharacter, enum EDumpStyle style) { - os << std::setprecision(std::numeric_limits::digits10 + 1); - ConfDumpInfo info(os, indent, indentCharacter, style); - switch (info.style) { - case CONF_STYLE: - s_confDumpType(info, dict); - break; - case JSON_STYLE: - s_jsonDumpTypeFirst(info, dict); - break; - } -} - -std::string dump(const blet::Dict& dict, std::size_t indent, char indentCharacter, enum EDumpStyle style) { - std::ostringstream oss(""); - dump(dict, oss, indent, indentCharacter, style); - return oss.str(); -} - -/** - * @brief structure of info parser - */ -struct ConfParseInfo { - ConfParseInfo(const std::string& filename_) : - filename(filename_) {} - ~ConfParseInfo() {} - - std::size_t line(std::size_t i) const { - return indexToLine[i]; - } - - std::size_t column(std::size_t i) const { - return i - lineToIndex[indexToLine[i] - 1] + 1; - } - - std::size_t lastLine(std::size_t /*i*/) const { - return indexToLine.back(); - } - - std::size_t lastColumn(std::size_t i) const { - return i + 1 - lineToIndex[indexToLine.back() - 1]; - } - - const std::string& filename; - std::vector indexToLine; - std::vector lineToIndex; - std::list currentSections; -}; - -static inline std::string s_replaceEscapeChar(const std::string& str) { - std::string ret(str); - for (std::size_t i = 0; i < ret.size(); ++i) { - if (ret[i] == '\\') { - switch (ret[i + 1]) { - case 'a': - ret.erase(i, 1); - ret[i] = '\a'; - break; - case 'b': - ret.erase(i, 1); - ret[i] = '\b'; - break; - case 'f': - ret.erase(i, 1); - ret[i] = '\f'; - break; - case 'n': - ret.erase(i, 1); - ret[i] = '\n'; - break; - case 'r': - ret.erase(i, 1); - ret[i] = '\r'; - break; - case 't': - ret.erase(i, 1); - ret[i] = '\t'; - break; - case 'v': - ret.erase(i, 1); - ret[i] = '\v'; - break; - case '\'': - ret.erase(i, 1); - ret[i] = '\''; - break; - case '"': - ret.erase(i, 1); - ret[i] = '\"'; - break; - case '\\': - ret.erase(i, 1); - ret[i] = '\\'; - break; - } - } - } - return ret; -} - -static inline void s_stringJumpSpace(const std::string& str, std::size_t& index) { - // isspace - while ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ') { - ++index; - } -} - -static inline void s_stringJumpSpaceNotNewLine(const std::string& str, std::size_t& index) { - // isspace - while (str[index] != '\n' && ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ')) { - ++index; - } -} - -static inline void s_stringReverseJumpSpace(const std::string& str, std::size_t& index) { - // isspace - while (index > 0 && ((str[index] >= '\t' && str[index] <= '\r') || str[index] == ' ')) { - --index; - } -} - -void s_parseType(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict); -void s_parseJsonArray(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict); - -static inline bool s_hex(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; - - if (value[index] == '-' || value[index] == '+') { - ++index; - } - // is hex - if (value[index] == '0' && (value[index + 1] == 'x' || value[index + 1] == 'X')) { - ++index; - ++index; - while (value[index] != '\0') { - if (value[index] >= '0' && value[index] <= '9') { - ++index; - } - else if (value[index] >= 'a' && value[index] <= 'f') { - ++index; - } - else { - return false; - } - } - stringStream << ::strtol(value.c_str(), NULL, 16); - return true; - } - else { - return false; - } -} - -static inline bool s_binary(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; - bool sub = false; - if (value[index] == '-' || value[index] == '+') { - if (value[index] == '-') { - sub = true; - } - ++index; - } - // is binary - if (value[index] == '0' && value[index + 1] == 'b') { - ++index; - ++index; - std::size_t start = index; - while (value[index] != '\0') { - if (value[index] == '0' || value[index] == '1') { - ++index; - } - else { - return false; - } - } - if (sub) { - long lvalue; - std::stringstream ss(""); - ss << '-' << ::strtoul(value.c_str() + start, NULL, 2); - ss >> lvalue; - stringStream << lvalue; - } - else { - stringStream << ::strtoul(value.c_str() + start, NULL, 2); - } - return true; - } - else { - return false; - } -} - -static inline bool s_octal(const std::string& value, std::ostream& stringStream) { - std::size_t index = 0; - - if (value[index] == '-' || value[index] == '+') { - ++index; - } - // is binary - if (value[index] == '0' && value.find('.') == std::string::npos && value.find('e') == std::string::npos) { - while (value[index] != '\0') { - if (value[index] >= '0' && value[index] <= '8') { - ++index; - } - else { - return false; - } - } - stringStream << ::strtol(value.c_str(), NULL, 8); - return true; - } - else { - return false; - } -} - -static inline bool s_double(const std::string& value, std::ostream& stringStream) { - char* endPtr = NULL; - stringStream << ::strtod(value.c_str(), &endPtr); - if (endPtr != NULL && endPtr[0] != '\0') { - return false; - } - return true; -} - -static inline void s_parseNumber(const std::string& str, blet::Dict& dict) { - std::stringstream stringStream(""); - if (s_hex(str, stringStream) || s_binary(str, stringStream) || s_octal(str, stringStream) || - s_double(str, stringStream)) { - double num; - stringStream >> num; - dict.newNumber(num); - } - else { - dict = str; - } -} - -static inline void s_parseValue(const std::string& str, blet::Dict& dict) { - std::string lowerStr(str); - for (std::size_t i = 0; i < lowerStr.size(); ++i) { - if (lowerStr[i] >= 'A' && lowerStr[i] <= 'Z') { - lowerStr[i] = lowerStr[i] + ' '; - } - } - switch (lowerStr[0]) { - case '-': - case '+': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - s_parseNumber(str, dict); - break; - case 'f': - if (lowerStr == "false") { - dict = false; - } - else { - dict = str; - } - break; - case 'n': - if (lowerStr == "no") { - dict = false; - } - else if (lowerStr == "null") { - dict.newNull(); - } - else { - dict = str; - } - break; - case 'o': - if (lowerStr == "on") { - dict = true; - } - else if (lowerStr == "off") { - dict = false; - } - else { - dict = str; - } - break; - case 't': - if (lowerStr == "true") { - dict = true; - } - else { - dict = str; - } - break; - case 'y': - if (lowerStr == "yes") { - dict = true; - } - else { - dict = str; - } - break; - default: - dict = str; - break; - } - if (dict.isString()) { - dict = s_replaceEscapeChar(dict.getValue().getString()); - } -} - -static inline void s_parseJsonObject(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - blet::Dict* currentDict; - ConfParseInfo valueInfo = info; - std::size_t start; - std::size_t end; - bool next = false; - - dict.newObject(); - ++i; // jump '{' - s_stringJumpSpace(str, i); - while (str[i] != '}' || next) { - if (str[i] == '\0') { - throw LoadException(info.filename, info.lastLine(i), info.lastColumn(i), "End of json object value"); - } - // parse key - if (str[i] == '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Key of json object value"); - } - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote at key in json object value"); - } - ++i; - } - end = i; - ++i; // jump quote - } - else { - start = i; - while (str[i] != '=') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key in json object value"); - } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - currentDict = &(dict.operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - valueInfo.currentSections.push_back(currentDict); - s_stringJumpSpace(str, i); - if (str[i] != '=') { - throw LoadException(info.filename, info.line(i), info.column(i), - "Operator = not found in json object value"); - } - ++i; // jump '=' - s_stringJumpSpace(str, i); - // start value - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote at value in json object value"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpace(str, i); - *currentDict = s_replaceEscapeChar(str.substr(start, end - start)); - } - else { - switch (str[i]) { - case '{': - s_parseJsonObject(str, valueInfo, i, *currentDict); - break; - case '[': - s_parseJsonArray(str, valueInfo, i, *currentDict); - break; - default: - start = i; - while (str[i] != ',' && str[i] != '}' && str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), *currentDict); - break; - } - } - s_stringJumpSpace(str, i); - if (str[i] == ',') { - ++i; // jump ',' - s_stringJumpSpace(str, i); - next = true; - } - else { - next = false; - } - } - ++i; // jump '}' - s_stringJumpSpace(str, i); -} - -void s_parseJsonArray(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - std::size_t start; - std::size_t end; - bool next = false; - - dict.newArray(); - ++i; // jump '[' - s_stringJumpSpace(str, i); - // parse value array - while (str[i] != ']' || next) { - if (str[i] == '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of json array value"); - } - // start value - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), - "End of quote in json array value"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ',' && str[i] != ']' && str[i] != '\n' && str[i] != '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of value in json array value"); - } - dict.push_back(s_replaceEscapeChar(str.substr(start, end - start))); - } - else { - dict.push_back(blet::Dict()); - info.currentSections.push_back(&(dict.back())); - switch (str[i]) { - case '{': - s_parseJsonObject(str, info, i, dict.back()); - break; - case '[': - s_parseJsonArray(str, info, i, dict.back()); - break; - default: - start = i; - while (str[i] != ',' && str[i] != ']' && str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), dict.back()); - } - } - s_stringJumpSpace(str, i); - if (str[i] == ',') { - ++i; // jump ',' - s_stringJumpSpace(str, i); - next = true; - } - else { - next = false; - } - } - ++i; // jump ']' - s_stringJumpSpace(str, i); -} - -static void s_parseSection(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - std::size_t level = 1; - std::size_t start; - std::size_t end; - ++i; // jump '[' - s_stringJumpSpaceNotNewLine(str, i); - // is multi section level - if (str[i] == '[') { - while (str[i] == '[') { - ++i; // jump '[' - ++level; - s_stringJumpSpaceNotNewLine(str, i); - } - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote multi section"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of multi section"); - } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - std::size_t maxlevel = level; - while (str[i] == ']') { - ++i; // jump ']' - --level; - s_stringJumpSpaceNotNewLine(str, i); - } - if (level != 0) { - throw LoadException(info.filename, info.line(i), info.column(i), "End of multi section"); - } - --maxlevel; - while (info.currentSections.size() > maxlevel) { - info.currentSections.pop_back(); - } - if (maxlevel == info.currentSections.size()) { - info.currentSections.push_back( - &(info.currentSections.back()->operator[](s_replaceEscapeChar(str.substr(start, end - start))))); - } - else { - throw LoadException(info.filename, info.line(i), info.column(i), "Multi section without parent"); - } - s_stringJumpSpaceNotNewLine(str, i); - } - else { - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote section"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of section"); - } - ++i; // jump ']' - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of section"); - } - ++i; - } - end = i - 1; - ++i; // jump ']' - s_stringReverseJumpSpace(str, end); - ++end; - } - info.currentSections.clear(); - if (end - start > 0) { - info.currentSections.push_back(&(dict[s_replaceEscapeChar(str.substr(start, end - start))])); - } - else { - info.currentSections.push_back(&dict); - } - s_stringJumpSpaceNotNewLine(str, i); - while (str[i] == '[') { - ++i; - s_stringJumpSpaceNotNewLine(str, i); - // start section name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote linear section"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of linear section"); - } - ++i; // jump ']' - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of linear section"); - } - ++i; - } - end = i - 1; - ++i; // jump ']' - s_stringReverseJumpSpace(str, end); - ++end; - } - info.currentSections.push_back( - &(info.currentSections.back()->operator[](s_replaceEscapeChar(str.substr(start, end - start))))); - s_stringJumpSpaceNotNewLine(str, i); - } - } - s_stringJumpSpace(str, i); -} - -inline void s_parseKeyValue(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - blet::Dict* currentDict = &dict; - ConfParseInfo valueInfo = info; - if (str[i] == '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Key not found"); - } - std::size_t start; - std::size_t end; - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '=' && str[i] != '[') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key"); - } - } - else { - start = i; - while (str[i] != '=' && str[i] != '[') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key"); - } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - currentDict = &(currentDict->operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - valueInfo.currentSections.push_back(currentDict); - // check table key - while (str[i] == '[') { - ++i; // jump '[' - s_stringJumpSpaceNotNewLine(str, i); - // start key name - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != ']') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of key of map"); - } - } - else { - start = i; - while (str[i] != ']') { - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of map"); - } - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - } - ++i; // jump ']' - s_stringJumpSpaceNotNewLine(str, i); - if (end - start > 0) { - currentDict = &(currentDict->operator[](s_replaceEscapeChar(str.substr(start, end - start)))); - } - else { - currentDict->push_back(blet::Dict()); - currentDict = &(currentDict->back()); - } - valueInfo.currentSections.push_back(currentDict); - } - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '=') { - throw LoadException(info.filename, info.line(i), info.column(i), "Operator = not found"); - } - ++i; // jump '=' - s_stringJumpSpaceNotNewLine(str, i); - // start value - if (str[i] == '\"' || str[i] == '\'') { - // get quote character - const char quote = str[i]; - ++i; // jump quote - start = i; - // search end quote - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; - } - if (str[i] == '\0' || str[i] == '\n') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of quote"); - } - ++i; - } - end = i; - ++i; // jump quote - s_stringJumpSpaceNotNewLine(str, i); - if (str[i] != '\n' && str[i] != '\0') { - throw LoadException(info.filename, info.line(i), info.column(i), "End of value"); - } - *currentDict = s_replaceEscapeChar(str.substr(start, end - start)); - } - else { - switch (str[i]) { - case '{': - s_parseJsonObject(str, valueInfo, i, *currentDict); - break; - case '[': - s_parseJsonArray(str, valueInfo, i, *currentDict); - break; - default: - start = i; - while (str[i] != '\n' && str[i] != '\0') { - ++i; - } - end = i - 1; - s_stringReverseJumpSpace(str, end); - ++end; - s_parseValue(str.substr(start, end - start), *currentDict); - break; - } - } - s_stringJumpSpace(str, i); -} - -inline void s_parseType(const std::string& str, ConfParseInfo& info, std::size_t& i, blet::Dict& dict) { - if (str[i] == '[') { - s_parseSection(str, info, i, dict); - } - else { - if (info.currentSections.empty()) { - info.currentSections.push_back(&dict); - } - s_parseKeyValue(str, info, i, *(info.currentSections.back())); - } -} - -static void s_replaceCommentBySpace(std::string& str) { - // replace double backslash - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - if (str[i] == '\\' && str[i + 1] == '\\') { - str[i] = replaceBackslash; - str[i + 1] = replaceBackslash; - } - } - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - // jump inside of string - if (str[i] == '"' || str[i] == '\'') { - const char quote = str[i]; - ++i; - while (str[i] != quote) { - if (str[i] == '\\' && (str[i + 1] == quote || str[i + 1] == '\\')) { - ++i; // escape character - } - if (str[i] == '\n' || str[i] == '\0') { - break; - } - ++i; - } - } - // replace - else if ((str[i] == '#' || str[i] == ';') && (i == 0 || (i > 0 && str[i - 1] != '\\'))) { - while (str[i] != '\0' && str[i] != '\n') { - str[i] = ' '; - ++i; - } - } - } - // replace double backslash - for (std::size_t i = 0; i < str.size() && str[i] != '\0'; ++i) { - if (str[i] == replaceBackslash && str[i + 1] == replaceBackslash) { - str[i] = '\\'; - str[i + 1] = '\\'; - } - } -} - -static std::string s_streamToStr(ConfParseInfo& info, std::istream& stream) { - // generate line and column index - std::ostringstream oss(""); - std::string line(""); - std::size_t nLine = 1; - info.lineToIndex.push_back(0); - while (std::getline(stream, line)) { - if (nLine > 1) { - oss << '\n'; - } - oss << line; - info.lineToIndex.push_back(line.size() + 1 + info.lineToIndex.back()); - info.indexToLine.insert(info.indexToLine.end(), line.size() + 1, nLine); - ++nLine; - } - return oss.str(); -} - -static blet::Dict s_loadStream(std::istream& stream, const std::string& filename) { - ConfParseInfo info(filename); - std::string str = s_streamToStr(info, stream); - s_replaceCommentBySpace(str); - std::size_t i = 0; - s_stringJumpSpace(str, i); - try { - blet::Dict dict; - while (str[i] != '\0') { - s_parseType(str, info, i, dict); - } - return dict; - } - catch (const LoadException& /*e*/) { - throw; - } -} - -blet::Dict loadFile(const char* filename) { - std::ifstream fileStream(filename); // open file - if (!fileStream.is_open()) { - throw LoadException(filename, "Open file failed"); - } - return s_loadStream(fileStream, filename); -} - -blet::Dict loadStream(std::istream& stream) { - return s_loadStream(stream, std::string()); -} - -blet::Dict loadString(const std::string& str) { - std::istringstream iss(str); - return loadStream(iss); -} - -blet::Dict loadData(const void* data, std::size_t size) { - return loadString(std::string(static_cast(data), size)); -} - -} // namespace conf - -} // namespace blet diff --git a/src/dump.cpp b/src/dump.cpp new file mode 100644 index 0000000..53d4e9d --- /dev/null +++ b/src/dump.cpp @@ -0,0 +1,575 @@ +/** + * dump.cpp + * + * Licensed under the MIT License . + * Copyright (c) 2024 BLET Mickaël. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include // std::setprecision +#include // std::numeric_limits + +#include "blet/conf.h" + +namespace blet { + +namespace conf { + +// ----------------------------------------------------------------------------- +// ................................. +// .#####...##..##..##...##..#####.. +// .##..##..##..##..###.###..##..##. +// .##..##..##..##..##.#.##..#####.. +// .##..##..##..##..##...##..##..... +// .#####....####...##...##..##..... +// ................................. +// ----------------------------------------------------------------------------- + +class Dumper { + public: + Dumper(std::ostream& os, std::size_t indent, char indentCharacter) : + os_(os), + indent_(indent), + indentCharacter_(indentCharacter), + indentIndex_(0) {} + + ~Dumper() {} + + void stringEscape(const std::string& str) { + for (std::size_t i = 0; i < str.size(); ++i) { + switch (str[i]) { + case '\a': + os_ << '\\' << 'a'; + break; + case '\b': + os_ << '\\' << 'b'; + break; + case '\f': + os_ << '\\' << 'f'; + break; + case '\n': + os_ << '\\' << 'n'; + break; + case '\r': + os_ << '\\' << 'r'; + break; + case '\t': + os_ << '\\' << 't'; + break; + case '\v': + os_ << '\\' << 'v'; + break; + case '\'': + os_ << '\\' << '\''; + break; + case '"': + os_ << '\\' << '"'; + break; + case '\\': + os_ << '\\' << '\\'; + break; + default: + os_ << str[i]; + break; + } + } + } + + void newlineDump(const blet::Dict& dict) { + if (indent_ != 0 && ((dict.isArray() && !dict.getValue().getArray().empty()) || + (dict.isObject() && !dict.getValue().getObject().empty()))) { + os_ << '\n'; + } + } + + void indentDump() { + if (indent_ != 0) { + os_ << std::string(indent_ * indentIndex_, indentCharacter_); + } + } + + void nullDump() { + os_ << "null"; + } + + void numberDump(const blet::Dict& dict) { + os_ << dict.getValue().getNumber(); + } + + void booleanDump(const blet::Dict& dict) { + if (dict.getValue().getBoolean()) { + os_ << "true"; + } + else { + os_ << "false"; + } + } + + void stringDump(const blet::Dict& dict) { + os_ << '"'; + stringEscape(dict.getValue().getString()); + os_ << '"'; + } + + protected: + static bool forceKeyString(const std::string& str) { + bool ret = false; + if (str.empty()) { + ret = true; + } + else { + for (std::size_t i = 0; ret == false && i < str.size(); ++i) { + switch (str[i]) { + case ' ': + case '=': + case ',': + case ';': + case '#': + case '[': + case ']': + case '{': + case '}': + case '"': + case '\'': + case '\a': + case '\b': + case '\f': + case '\r': + case '\t': + case '\v': + case '\\': + ret = true; + break; + default: + break; + } + } + } + return ret; + } + + std::ostream& os_; + std::size_t indent_; + char indentCharacter_; + std::size_t indentIndex_; +}; + +// ----------------------------------------------------------------------------- +// ......................................................................... +// .######...####....####...##..##..........#####...##..##..##...##..#####.. +// .....##..##......##..##..###.##..........##..##..##..##..###.###..##..##. +// .....##...####...##..##..##.###..........##..##..##..##..##.#.##..#####.. +// .##..##......##..##..##..##..##..........##..##..##..##..##...##..##..... +// ..####....####....####...##..##..........#####....####...##...##..##..... +// ......................................................................... +// ----------------------------------------------------------------------------- + +class JsonDumper : public Dumper { + public: + JsonDumper(std::ostream& os, std::size_t indent, char indentCharacter) : + Dumper(os, indent, indentCharacter) {} + + ~JsonDumper() {} + + void jsonDumpTypeFirst(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + case blet::Dict::BOOLEAN_TYPE: + case blet::Dict::NUMBER_TYPE: + case blet::Dict::STRING_TYPE: + jsonDumpType(dict); + break; + case blet::Dict::ARRAY_TYPE: + os_ << "\"\""; + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + jsonDumpArray(dict); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObjectFirst(dict); + break; + } + } + + void jsonDumpObjectFirst(const blet::Dict& dict) { + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + if (cit != dict.getValue().getObject().begin()) { + os_ << '\n'; + } + // key + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + jsonDumpType(cit->second); + } + } + + void jsonDumpType(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + nullDump(); + break; + case blet::Dict::BOOLEAN_TYPE: + booleanDump(dict); + break; + case blet::Dict::NUMBER_TYPE: + numberDump(dict); + break; + case blet::Dict::STRING_TYPE: + stringDump(dict); + break; + case blet::Dict::ARRAY_TYPE: + jsonDumpArray(dict); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObject(dict); + break; + } + } + + void jsonDumpArray(const blet::Dict& dict) { + if (dict.getValue().getArray().empty()) { + os_ << "[]"; + } + else { + os_ << '['; + newlineDump(dict); + ++indentIndex_; + for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { + if (i > 0) { + os_ << ','; + newlineDump(dict); + } + // value + indentDump(); + jsonDumpType(dict.getValue().getArray()[i]); + } + --indentIndex_; + newlineDump(dict); + indentDump(); + os_ << ']'; + } + } + + void jsonDumpObject(const blet::Dict& dict) { + if (dict.getValue().getObject().empty()) { + os_ << "{}"; + } + else { + os_ << '{'; + newlineDump(dict); + ++indentIndex_; + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + if (cit != dict.getValue().getObject().begin()) { + os_ << ','; + newlineDump(dict); + } + // key + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + jsonDumpType(cit->second); + } + --indentIndex_; + newlineDump(dict); + indentDump(); + os_ << '}'; + } + } +}; + +// ----------------------------------------------------------------------------- +// ......................................................................... +// ..####....####...##..##..######..........#####...##..##..##...##..#####.. +// .##..##..##..##..###.##..##..............##..##..##..##..###.###..##..##. +// .##......##..##..##.###..####............##..##..##..##..##.#.##..#####.. +// .##..##..##..##..##..##..##..............##..##..##..##..##...##..##..... +// ..####....####...##..##..##..............#####....####...##...##..##..... +// ......................................................................... +// ----------------------------------------------------------------------------- + +class ConfDumper : public JsonDumper { + public: + ConfDumper(std::ostream& os, std::size_t indent, char indentCharacter) : + JsonDumper(os, indent, indentCharacter), + sectionIndex_(0) {} + + ~ConfDumper() {} + + void confDumpType(const blet::Dict& dict) { + switch (dict.getType()) { + case blet::Dict::NULL_TYPE: + nullDump(); + break; + case blet::Dict::BOOLEAN_TYPE: + booleanDump(dict); + break; + case blet::Dict::NUMBER_TYPE: + numberDump(dict); + break; + case blet::Dict::STRING_TYPE: + stringDump(dict); + break; + case blet::Dict::ARRAY_TYPE: + confDumpArray("", dict); + break; + case blet::Dict::OBJECT_TYPE: + confDumpObject(dict); + break; + } + } + + void confDumpArray(const std::string& key, const blet::Dict& dict) { + if (dict.getValue().getArray().empty()) { + // key + indentDump(); + if (forceKeyString(key)) { + os_ << '"'; + stringEscape(key); + os_ << '"'; + } + else { + stringEscape(key); + } + // value operator + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + os_ << "[]"; + } + else { + for (std::size_t i = 0; i < dict.getValue().getArray().size(); ++i) { + if (i > 0) { + os_ << '\n'; + } + // key + indentDump(); + if (forceKeyString(key)) { + os_ << '"'; + stringEscape(key); + os_ << '"'; + } + else { + stringEscape(key); + } + // array operator + os_ << "[]"; + // value operator + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + // value + switch (dict.getValue().getArray()[i].getType()) { + case blet::Dict::NULL_TYPE: + case blet::Dict::BOOLEAN_TYPE: + case blet::Dict::NUMBER_TYPE: + case blet::Dict::STRING_TYPE: + confDumpType(dict.getValue().getArray()[i]); + break; + case blet::Dict::ARRAY_TYPE: + jsonDumpArray(dict.getValue().getArray()[i]); + break; + case blet::Dict::OBJECT_TYPE: + jsonDumpObject(dict.getValue().getArray()[i]); + break; + } + } + } + } + + void confDumpObject(const blet::Dict& dict) { + int index = 0; + ++sectionIndex_; + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + switch (cit->second.getType()) { + case blet::Dict::NULL_TYPE: + case blet::Dict::BOOLEAN_TYPE: + case blet::Dict::NUMBER_TYPE: + case blet::Dict::STRING_TYPE: + if (index > 0) { + os_ << '\n'; + } + ++index; + indentDump(); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + confDumpType(cit->second); + break; + case blet::Dict::ARRAY_TYPE: + if (index > 0) { + os_ << '\n'; + } + ++index; + confDumpArray(cit->first, cit->second); + break; + case blet::Dict::OBJECT_TYPE: + break; + } + } + for (blet::Dict::object_t::const_iterator cit = dict.getValue().getObject().begin(); + cit != dict.getValue().getObject().end(); ++cit) { + if (cit->second.getType() == blet::Dict::OBJECT_TYPE) { + if (index > 0) { + os_ << '\n'; + } + ++index; + if (cit->second.getValue().getObject().empty()) { + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + if (indent_ > 0) { + os_ << ' '; + } + os_ << '='; + if (indent_ > 0) { + os_ << ' '; + } + os_ << "{}"; + } + else { + // new section + os_ << std::string(sectionIndex_, '['); + if (forceKeyString(cit->first)) { + os_ << '"'; + stringEscape(cit->first); + os_ << '"'; + } + else { + stringEscape(cit->first); + } + os_ << std::string(sectionIndex_, ']'); + os_ << '\n'; + confDumpType(cit->second); + } + } + } + --sectionIndex_; + } + + private: + std::size_t sectionIndex_; +}; + +/** + * @brief structure of info dumper + */ +struct ConfDumpInfo { + ConfDumpInfo(std::ostream& os_, std::size_t& indent_, char& indentCharacter_, enum EDumpStyle& style_) : + os(os_), + indent(indent_), + indentCharacter(indentCharacter_), + style(style_), + indexSection(0), + index(0) {} + ~ConfDumpInfo() {} + + std::ostream& os; + std::size_t& indent; + char& indentCharacter; + enum EDumpStyle& style; + std::size_t indexSection; + std::size_t index; +}; + +void dump(const blet::Dict& dict, std::ostream& os, std::size_t indent, char indentCharacter, enum EDumpStyle style) { + os << std::setprecision(std::numeric_limits::digits10 + 1); + switch (style) { + case CONF_STYLE: { + ConfDumper conf(os, indent, indentCharacter); + conf.confDumpType(dict); + break; + } + case JSON_STYLE: { + JsonDumper conf(os, indent, indentCharacter); + conf.jsonDumpTypeFirst(dict); + break; + } + } +} + +std::string dump(const blet::Dict& dict, std::size_t indent, char indentCharacter, enum EDumpStyle style) { + std::ostringstream oss(""); + dump(dict, oss, indent, indentCharacter, style); + return oss.str(); +} + +} // namespace conf + +} // namespace blet diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 0000000..38747a2 --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,87 @@ +/** + * exception.cpp + * + * Licensed under the MIT License . + * Copyright (c) 2024 BLET Mickaël. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "blet/conf.h" + +namespace blet { + +namespace conf { + +LoadException::LoadException(const std::string& filename, const std::string& message) : + std::exception(), + filename_(filename), + message_(message), + line_(0), + column_(0) { + std::ostringstream oss(""); + oss << "Load "; + if (!filename_.empty()) { + oss << filename_ << ": "; + } + oss << '(' << message_ << ")"; + what_ = oss.str(); +} + +LoadException::LoadException(const std::string& filename, std::size_t line, std::size_t column, + const std::string& message) : + std::exception(), + filename_(filename), + message_(message), + line_(line), + column_(column) { + std::ostringstream oss(""); + oss << "Load at "; + if (!filename_.empty()) { + oss << filename_ << ':'; + } + oss << line_ << ':' << column_ << " (" << message_ << ")"; + what_ = oss.str(); +} + +LoadException::~LoadException() throw() {} + +const char* LoadException::what() const throw() { + return what_.c_str(); +} + +const std::string& LoadException::filename() const throw() { + return filename_; +} + +const std::string& LoadException::message() const throw() { + return message_; +} + +const std::size_t& LoadException::line() const throw() { + return line_; +} + +const std::size_t& LoadException::column() const throw() { + return column_; +} + +} // namespace conf + +} // namespace blet diff --git a/src/load.cpp b/src/load.cpp new file mode 100644 index 0000000..c972c58 --- /dev/null +++ b/src/load.cpp @@ -0,0 +1,780 @@ +/** + * load.cpp + * + * Licensed under the MIT License . + * Copyright (c) 2024 BLET Mickaël. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include // std::ifstream + +#include "blet/conf.h" + +namespace blet { + +namespace conf { + +class StringReader { + public: + StringReader(std::istream& stream) : + stream_(stream), + pos_(0), + line_(1), + posColumn_(0) { + stream_.seekg(0, stream_.end); + std::size_t streamLength = stream_.tellg(); + stream_.seekg(0, stream_.beg); + std::vector vBuffer(streamLength); + stream_.read(vBuffer.data(), vBuffer.size()); + str_.append(vBuffer.begin(), vBuffer.end()); + } + + ~StringReader() {} + + template + bool operator==(const char (&str)[Size]) { + return !str_.compare(pos_, Size - 1, str, Size - 1); + } + + void operator++() { + if (str_[pos_] == '\n') { + posColumn_ = pos_ + 1; + ++line_; + } + ++pos_; + } + + void operator+=(std::size_t index) { + for (std::size_t i = 0; i < index; ++i) { + operator++(); + } + } + + const char& operator[](std::size_t index) { + return str_[pos_ + index]; + } + + std::string substr(std::size_t start, std::size_t end) { + return str_.substr(start, end - start); + } + + std::size_t line() { + return line_; + } + + std::size_t column() { + return pos_ - posColumn_ + 1; + } + + std::size_t index() { + return pos_; + } + + std::istream& streamOffset(std::size_t offset) { + return stream_.seekg(offset, stream_.beg); + } + + private: + std::istream& stream_; + std::string str_; + std::size_t pos_; + std::size_t line_; + std::size_t posColumn_; +}; + +class Loader { + public: + Loader(const std::string& filename, std::istream& stream) : + filename_(filename), + reader_(stream) {} + + ~Loader() {} + + enum EValueFromType { + DEFAULT_VALUE_FROM_TYPE, + OBJECT_VALUE_FROM_TYPE, + ARRAY_VALUE_FROM_TYPE + }; + + void load(blet::Dict& dict) { + spaceJump(); + while (reader_[0] != '\0') { + loadType(dict); + } + } + + void loadType(blet::Dict& dict) { + if (reader_[0] == '[') { + loadSection(dict); + } + else if (reader_[0] == '{') { + if (currentSections_.empty()) { + loadObject(dict); + } + else { + loadObject(*(currentSections_.top())); + } + } + else { + // create the default section if needed + if (currentSections_.empty()) { + currentSections_.push(&dict); + } + loadKey(*(currentSections_.top())); + } + } + + void loadSection(blet::Dict& dict, bool linear = false) { + ++reader_; // jump '[' + spaceJumpLine(); + // multi section + if (reader_[0] == '[') { + std::size_t level = 1; + while (reader_[0] == '[') { + ++reader_; // jump '[' + ++level; + spaceJumpLine(); + } + std::string sectionName = loadSectionName(); + std::size_t maxlevel = level - 1; + while (reader_[0] == ']') { + ++reader_; // jump ']' + --level; + spaceJumpLine(); + } + if (level != 0) { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); + } + while (currentSections_.size() > maxlevel) { + currentSections_.pop(); + } + if (maxlevel == currentSections_.size()) { + currentSections_.push(&(currentSections_.top()->operator[](sectionName))); + } + else { + throw LoadException(filename_, reader_.line(), reader_.column(), "Section without parent"); + } + } + // basic or linear section + else { + if (!linear) { + // clear currentSections_ + currentSections_ = std::stack(); + } + std::string sectionName = loadSectionName(); + if (sectionName.empty()) { + // set the default section + currentSections_.push(&dict); + } + else { + // add new section + currentSections_.push(&(dict[sectionName])); + } + ++reader_; // jump ']' + } + spaceJumpLine(); + if (reader_[0] == '[') { + // recursive linear + loadSection(*(currentSections_.top()), true); + } + spaceJump(); + } + + std::string loadSectionName() { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + // section name + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != ']') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); + } + return stringEscape(reader_.substr(start, end)); + } + else { + while (reader_[0] != ']') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of section"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } + return reader_.substr(start, end); + } + } + + void loadKey(blet::Dict& dict) { + if (reader_[0] == '=' || reader_[0] == ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Key not found"); + } + blet::Dict* pKeyDict = loadKeyDict(dict); + spaceJump(); + if (reader_[0] != '=' && reader_[0] != ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + ++reader_; // jump '=' or ':' + spaceJumpLine(); + loadValue(*pKeyDict); + spaceJump(); + } + + blet::Dict* loadKeyDict(blet::Dict& dict) { + blet::Dict* pCurrentDict = &(dict[loadKeyName()]); + // map key + while (reader_[0] == '[') { + std::string keyMapName = loadKeyMapName(); + if (keyMapName.empty()) { + pCurrentDict->push_back(blet::Dict()); + pCurrentDict = &(pCurrentDict->back()); + } + else { + pCurrentDict = &(pCurrentDict->operator[](keyMapName)); + } + } + return pCurrentDict; + } + + std::string loadKeyName() { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != '=' && reader_[0] != ':' && reader_[0] != '[') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + } + else { + while (reader_[0] != '=' && reader_[0] != ':' && reader_[0] != '[') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } + } + return stringEscape(reader_.substr(start, end)); + } + + std::string loadKeyMapName() { + ++reader_; // jump '[' + spaceJumpLine(); + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + if (reader_[0] == '"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + if (reader_[0] != ']') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of map"); + } + } + else { + while (reader_[0] != ']') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of map"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } + } + ++reader_; // jump ']' + spaceJumpLine(); + return stringEscape(reader_.substr(start, end)); + } + + void loadValue(blet::Dict& dict, EValueFromType fromType = DEFAULT_VALUE_FROM_TYPE) { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + switch (reader_[0]) { + case '"': + case '\'': { + loadQuoteIndexes(&start, &end); + spaceJumpLine(); + commentJump(); + if (reader_[0] != '\n' && reader_[0] != '\0') { + if (fromType == DEFAULT_VALUE_FROM_TYPE || + (fromType == OBJECT_VALUE_FROM_TYPE && reader_[0] != '}' && reader_[0] != ',') || + (fromType == ARRAY_VALUE_FROM_TYPE && reader_[0] != ']' && reader_[0] != ',')) { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of value"); + } + } + dict.clear(); + dict = stringEscape(reader_.substr(start, end)); + break; + } + case '{': { + loadObject(dict); + break; + } + case '[': { + loadArray(dict); + break; + } + default: { + while (reader_[0] != '\n' && reader_[0] != ';' && reader_[0] != '#' && reader_[0] != '\0') { + if (fromType == OBJECT_VALUE_FROM_TYPE && (reader_[0] == '}' || reader_[0] == ',')) { + break; + } + if (fromType == ARRAY_VALUE_FROM_TYPE && (reader_[0] == ']' || reader_[0] == ',')) { + break; + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } + dict.clear(); + getValue(dict, reader_.substr(start, end)); + break; + } + } + } + + void loadObject(blet::Dict& dict) { + std::size_t start = reader_.index(); + std::size_t end = reader_.index(); + bool next = false; + + if (!dict.isObject()) { + dict.newObject(); + } + ++reader_; // jump '{' + spaceJump(); + while (reader_[0] != '}' || next) { + if (reader_[0] == '\0') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of object"); + } + if (reader_[0] == '=' || reader_[0] == ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Key not found"); + } + // start key name + if (reader_[0] == '\"' || reader_[0] == '\'') { + loadQuoteIndexes(&start, &end); + } + else { + start = reader_.index(); + while (reader_[0] != '=' && reader_[0] != ':') { + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of key"); + } + if ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + spaceJumpLine(); + } + else { + ++reader_; + end = reader_.index(); + } + } + } + blet::Dict* objDict = &(dict.operator[](stringEscape(reader_.substr(start, end)))); + spaceJump(); + if (reader_[0] != '=' && reader_[0] != ':') { + throw LoadException(filename_, reader_.line(), reader_.column(), "Assign operator not found"); + } + ++reader_; // jump '=' or ':' + spaceJump(); + // recursive + loadValue(*objDict, OBJECT_VALUE_FROM_TYPE); + spaceJump(); + if (reader_[0] == ',') { + ++reader_; // jump ',' + spaceJump(); + next = true; + } + else { + next = false; + } + } + ++reader_; // jump '}' + spaceJump(); + } + + void loadArray(blet::Dict& dict) { + bool next = false; + + dict.newArray(); + ++reader_; // jump '[]' + spaceJump(); + while (reader_[0] != ']' || next) { + if (reader_[0] == '\0') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of array"); + } + dict.push_back(blet::Dict()); + blet::Dict& arrDict = dict.back(); + // recursive + loadValue(arrDict, ARRAY_VALUE_FROM_TYPE); + spaceJump(); + if (reader_[0] == ',') { + ++reader_; // jump ',' + spaceJump(); + next = true; + } + else { + next = false; + } + } + ++reader_; // jump '}' + spaceJump(); + } + + void getValue(blet::Dict& dict, const std::string& value) { + std::string lowerStr(value); + for (std::size_t i = 0; i < lowerStr.size(); ++i) { + if (lowerStr[i] >= 'A' && lowerStr[i] <= 'Z') { + lowerStr[i] = lowerStr[i] + ' '; + } + } + switch (lowerStr[0]) { + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + s_parseNumber(value, dict); + break; + case 'f': + if (lowerStr == "false") { + dict = false; + } + else { + dict = value; + } + break; + case 'n': + if (lowerStr == "no") { + dict = false; + } + else if (lowerStr == "none" || lowerStr == "null") { + dict.newNull(); + } + else { + dict = value; + } + break; + case 'o': + if (lowerStr == "on") { + dict = true; + } + else if (lowerStr == "off") { + dict = false; + } + else { + dict = value; + } + break; + case 't': + if (lowerStr == "true") { + dict = true; + } + else { + dict = value; + } + break; + case 'y': + if (lowerStr == "yes") { + dict = true; + } + else { + dict = value; + } + break; + case '\0': + dict.newNull(); + break; + default: + dict = value; + break; + } + } + + void loadQuoteIndexes(std::size_t* pStart, std::size_t* pEnd) { + // get quote character + const char quote = reader_[0]; + ++reader_; // jump quote + *pStart = reader_.index(); + // search end quote + while (reader_[0] != quote) { + if (reader_[0] == '\\' && (reader_[1] == quote || reader_[1] == '\\')) { + ++reader_; + } + if (reader_[0] == '\0' || reader_[0] == '\n') { + throw LoadException(filename_, reader_.line(), reader_.column(), "End of quote"); + } + ++reader_; + } + *pEnd = reader_.index(); + ++reader_; // jump quote + } + + void spaceJumpLine() { + // isspace + while (reader_[0] != '\n' && ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ')) { + ++reader_; + } + } + + void spaceJump() { + commentJump(); + // isspace + while ((reader_[0] >= '\t' && reader_[0] <= '\r') || reader_[0] == ' ') { + ++reader_; + commentJump(); + } + } + + void commentJump() { + if (reader_[0] == '#' || reader_[0] == ';') { + while (reader_[0] != '\0' && reader_[0] != '\n') { + ++reader_; // jump character + } + } + } + + static std::string stringEscape(const std::string& str) { + std::ostringstream oss(""); + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == '\\') { + switch (str[i + 1]) { + case 'a': + oss << '\a'; + break; + case 'b': + oss << '\b'; + break; + case 'f': + oss << '\f'; + break; + case 'n': + oss << '\n'; + break; + case 'r': + oss << '\r'; + break; + case 't': + oss << '\t'; + break; + case 'v': + oss << '\v'; + break; + case '\'': + oss << '\''; + break; + case '"': + oss << '\"'; + break; + case '\\': + oss << '\\'; + break; + default: + oss << '\\'; + if (str[i + 1] != '\0') { + oss << str[i + 1]; + } + break; + } + ++i; + } + else { + oss << str[i]; + } + } + return oss.str(); + } + + private: + static bool s_hex(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + + if (value[index] == '-' || value[index] == '+') { + ++index; + } + // is hex + if (value[index] == '0' && (value[index + 1] == 'x' || value[index + 1] == 'X')) { + ++index; + ++index; + while (value[index] != '\0') { + if (value[index] >= '0' && value[index] <= '9') { + ++index; + } + else if (value[index] >= 'a' && value[index] <= 'f') { + ++index; + } + else { + return false; + } + } + stringStream << ::strtol(value.c_str(), NULL, 16); + return true; + } + else { + return false; + } + } + + static bool s_binary(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + bool sub = false; + if (value[index] == '-' || value[index] == '+') { + if (value[index] == '-') { + sub = true; + } + ++index; + } + // is binary + if (value[index] == '0' && value[index + 1] == 'b') { + ++index; + ++index; + std::size_t start = index; + while (value[index] != '\0') { + if (value[index] == '0' || value[index] == '1') { + ++index; + } + else { + return false; + } + } + if (sub) { + long lvalue; + std::stringstream ss(""); + ss << '-' << ::strtoul(value.c_str() + start, NULL, 2); + ss >> lvalue; + stringStream << lvalue; + } + else { + stringStream << ::strtoul(value.c_str() + start, NULL, 2); + } + return true; + } + else { + return false; + } + } + + static bool s_octal(const std::string& value, std::ostream& stringStream) { + std::size_t index = 0; + + if (value[index] == '-' || value[index] == '+') { + ++index; + } + // is binary + if (value[index] == '0' && value.find('.') == std::string::npos && value.find('e') == std::string::npos) { + while (value[index] != '\0') { + if (value[index] >= '0' && value[index] <= '8') { + ++index; + } + else { + return false; + } + } + stringStream << ::strtol(value.c_str(), NULL, 8); + return true; + } + else { + return false; + } + } + + static bool s_double(const std::string& value, std::ostream& stringStream) { + char* endPtr = NULL; + stringStream << ::strtod(value.c_str(), &endPtr); + if (endPtr != NULL && endPtr[0] != '\0') { + return false; + } + return true; + } + + static void s_parseNumber(const std::string& str, blet::Dict& dict) { + std::stringstream stringStream(""); + if (s_hex(str, stringStream) || s_binary(str, stringStream) || s_octal(str, stringStream) || + s_double(str, stringStream)) { + double num; + stringStream >> num; + dict.newNumber(num); + } + else { + dict = str; + } + } + + const std::string filename_; + StringReader reader_; + std::stack currentSections_; +}; + +blet::Dict loadFile(const char* filename) { + std::ifstream fileStream(filename); // open file + if (!fileStream.is_open()) { + throw LoadException(filename, "Open file failed"); + } + blet::Dict dict; + Loader loader(filename, fileStream); + loader.load(dict); + return dict; +} + +blet::Dict loadStream(std::istream& stream) { + blet::Dict dict; + Loader loader("", stream); + loader.load(dict); + return dict; +} + +blet::Dict loadString(const std::string& str) { + std::istringstream iss(str); + return loadStream(iss); +} + +blet::Dict loadData(const void* data, std::size_t size) { + return loadString(std::string(static_cast(data), size)); +} + +} // namespace conf + +} // namespace blet diff --git a/test/example.cpp b/test/example.cpp index a8a45d2..dcac5ca 100644 --- a/test/example.cpp +++ b/test/example.cpp @@ -102,6 +102,8 @@ GTEST_TEST(configator, test5) { blet::Dict conf = blet::conf::loadString(strConf); + std::cout << blet::conf::dump(conf, 2, ' ', blet::conf::JSON_STYLE) << std::endl; + EXPECT_EQ(conf["test"]["32[\\]"].getString(), "\"2 ;\"#'## i\\"); EXPECT_EQ(conf["test"]["42"]["key[]"].getString(), "value"); EXPECT_EQ(conf["test"]["42 space "]["key[]"].getString(), " value space "); diff --git a/test/exception.cpp b/test/exception.cpp index 444cd9e..ba49634 100644 --- a/test/exception.cpp +++ b/test/exception.cpp @@ -9,8 +9,8 @@ GTEST_TEST(loadString, end_of_section) { blet::Dict conf = blet::conf::loadString("['test]"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:8 (End of quote section)"); - EXPECT_EQ(e.message(), "End of quote section"); + EXPECT_STREQ(e.what(), "Load at 1:8 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 8); @@ -24,7 +24,7 @@ GTEST_TEST(loadString, end_of_section) { blet::Dict conf = blet::conf::loadString("[test"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:6 (End of section)"); + EXPECT_STREQ(e.what(), "Load at 1:6 (End of section)"); EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -39,7 +39,7 @@ GTEST_TEST(loadString, end_of_section) { blet::Dict conf = blet::conf::loadString("['test'"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:8 (End of section)"); + EXPECT_STREQ(e.what(), "Load at 1:8 (End of section)"); EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -57,8 +57,8 @@ GTEST_TEST(loadString, end_of_multi_section) { blet::Dict conf = blet::conf::loadString("[['test]"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:9 (End of quote multi section)"); - EXPECT_EQ(e.message(), "End of quote multi section"); + EXPECT_STREQ(e.what(), "Load at 1:9 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 9); @@ -72,8 +72,8 @@ GTEST_TEST(loadString, end_of_multi_section) { blet::Dict conf = blet::conf::loadString("[[test"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:7 (End of multi section)"); - EXPECT_EQ(e.message(), "End of multi section"); + EXPECT_STREQ(e.what(), "Load at 1:7 (End of section)"); + EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 7); @@ -87,8 +87,8 @@ GTEST_TEST(loadString, end_of_multi_section) { blet::Dict conf = blet::conf::loadString("[[test]]]"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:10 (End of multi section)"); - EXPECT_EQ(e.message(), "End of multi section"); + EXPECT_STREQ(e.what(), "Load at 1:10 (End of section)"); + EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 10); @@ -105,8 +105,8 @@ GTEST_TEST(loadString, multi_section_without_parent) { blet::Dict conf = blet::conf::loadString("[[test]]"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:9 (Multi section without parent)"); - EXPECT_EQ(e.message(), "Multi section without parent"); + EXPECT_STREQ(e.what(), "Load at 1:9 (Section without parent)"); + EXPECT_EQ(e.message(), "Section without parent"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 9); @@ -123,8 +123,8 @@ GTEST_TEST(loadString, end_of_linear_section) { blet::Dict conf = blet::conf::loadString("[test]['test"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:13 (End of quote linear section)"); - EXPECT_EQ(e.message(), "End of quote linear section"); + EXPECT_STREQ(e.what(), "Load at 1:13 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 13); @@ -138,8 +138,8 @@ GTEST_TEST(loadString, end_of_linear_section) { blet::Dict conf = blet::conf::loadString("[test]['test'"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:14 (End of linear section)"); - EXPECT_EQ(e.message(), "End of linear section"); + EXPECT_STREQ(e.what(), "Load at 1:14 (End of section)"); + EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 14); @@ -153,8 +153,8 @@ GTEST_TEST(loadString, end_of_linear_section) { blet::Dict conf = blet::conf::loadString("[test][test"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:12 (End of linear section)"); - EXPECT_EQ(e.message(), "End of linear section"); + EXPECT_STREQ(e.what(), "Load at 1:12 (End of section)"); + EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 12); @@ -168,8 +168,8 @@ GTEST_TEST(loadString, end_of_linear_section) { blet::Dict conf = blet::conf::loadString("[[test]]]"); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:10 (End of multi section)"); - EXPECT_EQ(e.message(), "End of multi section"); + EXPECT_STREQ(e.what(), "Load at 1:10 (End of section)"); + EXPECT_EQ(e.message(), "End of section"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 10); @@ -186,7 +186,7 @@ GTEST_TEST(loadString, key_not_found) { blet::Dict conf = blet::conf::loadString("="); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:1 (Key not found)"); + EXPECT_STREQ(e.what(), "Load at 1:1 (Key not found)"); EXPECT_EQ(e.message(), "Key not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -207,7 +207,7 @@ GTEST_TEST(loadString, end_of_quote) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:6 (End of quote)"); + EXPECT_STREQ(e.what(), "Load at 1:6 (End of quote)"); EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -225,7 +225,7 @@ GTEST_TEST(loadString, end_of_quote) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:11 (End of quote)"); + EXPECT_STREQ(e.what(), "Load at 1:11 (End of quote)"); EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -243,7 +243,7 @@ GTEST_TEST(loadString, end_of_quote) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:11 (End of quote)"); + EXPECT_STREQ(e.what(), "Load at 1:11 (End of quote)"); EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -264,8 +264,8 @@ GTEST_TEST(loadString, end_of_key) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:7 (End of key)"); - EXPECT_EQ(e.message(), "End of key"); + EXPECT_STREQ(e.what(), "Load at 1:7 (Assign operator not found)"); + EXPECT_EQ(e.message(), "Assign operator not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 7); @@ -282,8 +282,8 @@ GTEST_TEST(loadString, end_of_key) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:5 (End of key)"); - EXPECT_EQ(e.message(), "End of key"); + EXPECT_STREQ(e.what(), "Load at 1:5 (Assign operator not found)"); + EXPECT_EQ(e.message(), "Assign operator not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 5); @@ -303,8 +303,8 @@ GTEST_TEST(loadString, end_of_map_key) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:12 (End of key of map)"); - EXPECT_EQ(e.message(), "End of key of map"); + EXPECT_STREQ(e.what(), "Load at 1:12 (End of map)"); + EXPECT_EQ(e.message(), "End of map"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 12); @@ -324,7 +324,7 @@ GTEST_TEST(loadString, end_of_map) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:10 (End of map)"); + EXPECT_STREQ(e.what(), "Load at 1:10 (End of map)"); EXPECT_EQ(e.message(), "End of map"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -345,8 +345,8 @@ GTEST_TEST(loadString, operator_EQ_not_found) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:11 (Operator = not found)"); - EXPECT_EQ(e.message(), "Operator = not found"); + EXPECT_STREQ(e.what(), "Load at 1:11 (Assign operator not found)"); + EXPECT_EQ(e.message(), "Assign operator not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 11); @@ -366,7 +366,7 @@ GTEST_TEST(loadString, end_of_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:12 (End of value)"); + EXPECT_STREQ(e.what(), "Load at 1:12 (End of value)"); EXPECT_EQ(e.message(), "End of value"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); @@ -387,8 +387,8 @@ GTEST_TEST(loadString, end_of_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:7 (End of json object value)"); - EXPECT_EQ(e.message(), "End of json object value"); + EXPECT_STREQ(e.what(), "Load at 1:7 (End of object)"); + EXPECT_EQ(e.message(), "End of object"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 7); @@ -408,8 +408,8 @@ GTEST_TEST(loadString, key_of_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:7 (Key of json object value)"); - EXPECT_EQ(e.message(), "Key of json object value"); + EXPECT_STREQ(e.what(), "Load at 1:7 (Key not found)"); + EXPECT_EQ(e.message(), "Key not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 7); @@ -429,8 +429,8 @@ GTEST_TEST(loadString, end_of_quote_at_key_in_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:12 (End of quote at key in json object value)"); - EXPECT_EQ(e.message(), "End of quote at key in json object value"); + EXPECT_STREQ(e.what(), "Load at 1:12 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 12); @@ -450,8 +450,8 @@ GTEST_TEST(loadString, end_of_key_in_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:11 (End of key in json object value)"); - EXPECT_EQ(e.message(), "End of key in json object value"); + EXPECT_STREQ(e.what(), "Load at 1:11 (End of key)"); + EXPECT_EQ(e.message(), "End of key"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 11); @@ -471,8 +471,8 @@ GTEST_TEST(loadString, operator_EQ_not_found_in_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:13 (Operator = not found in json object value)"); - EXPECT_EQ(e.message(), "Operator = not found in json object value"); + EXPECT_STREQ(e.what(), "Load at 1:13 (Assign operator not found)"); + EXPECT_EQ(e.message(), "Assign operator not found"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 13); @@ -492,8 +492,8 @@ GTEST_TEST(loadString, end_of_quote_at_value_in_json_object_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:19 (End of quote at value in json object value)"); - EXPECT_EQ(e.message(), "End of quote at value in json object value"); + EXPECT_STREQ(e.what(), "Load at 1:19 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 19); @@ -513,8 +513,8 @@ GTEST_TEST(loadString, end_of_json_array_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:7 (End of json array value)"); - EXPECT_EQ(e.message(), "End of json array value"); + EXPECT_STREQ(e.what(), "Load at 1:7 (End of array)"); + EXPECT_EQ(e.message(), "End of array"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 7); @@ -534,8 +534,8 @@ GTEST_TEST(loadString, end_of_quote_in_json_array_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:12 (End of quote in json array value)"); - EXPECT_EQ(e.message(), "End of quote in json array value"); + EXPECT_STREQ(e.what(), "Load at 1:12 (End of quote)"); + EXPECT_EQ(e.message(), "End of quote"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 12); @@ -555,8 +555,8 @@ GTEST_TEST(loadString, end_of_value_in_json_array_value) { blet::Dict conf = blet::conf::loadString(jsonStr); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at 1:13 (End of value in json array value)"); - EXPECT_EQ(e.message(), "End of value in json array value"); + EXPECT_STREQ(e.what(), "Load at 1:13 (End of value)"); + EXPECT_EQ(e.message(), "End of value"); EXPECT_EQ(e.filename(), ""); EXPECT_EQ(e.line(), 1); EXPECT_EQ(e.column(), 13); diff --git a/test/loadFile.cpp b/test/loadFile.cpp index fdfb139..8436f8a 100644 --- a/test/loadFile.cpp +++ b/test/loadFile.cpp @@ -16,7 +16,7 @@ GTEST_TEST(loadFile, except_open_file) { blet::Dict conf = blet::conf::loadFile(testFile); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse /tmp/blet_test_loadFile_except_open_file.conf: (Open file failed)"); + EXPECT_STREQ(e.what(), "Load /tmp/blet_test_loadFile_except_open_file.conf: (Open file failed)"); EXPECT_EQ(e.message(), "Open file failed"); EXPECT_EQ(e.filename(), "/tmp/blet_test_loadFile_except_open_file.conf"); EXPECT_EQ(e.line(), 0); @@ -46,7 +46,7 @@ GTEST_TEST(loadFile, except_parsing) { blet::Dict conf = blet::conf::loadFile(testFile); } catch (const blet::conf::LoadException& e) { - EXPECT_STREQ(e.what(), "Parse at /tmp/blet_test_loadFile_except_parsing.conf:2:1 (Key not found)"); + EXPECT_STREQ(e.what(), "Load at /tmp/blet_test_loadFile_except_parsing.conf:2:1 (Key not found)"); EXPECT_EQ(e.message(), "Key not found"); EXPECT_EQ(e.filename(), "/tmp/blet_test_loadFile_except_parsing.conf"); EXPECT_EQ(e.line(), 2); @@ -56,3 +56,20 @@ GTEST_TEST(loadFile, except_parsing) { }, blet::conf::LoadException); } + +GTEST_TEST(loadFile, valid) { + // clang-format off + std::string confStr = "" + "[section]\n" + "test=42"; + // clang-format on + + // create example file + const char* testFile = "/tmp/blet_test_loadFile_valid.conf"; + test::blet::FileGuard fileGuard(testFile, std::ofstream::out | std::ofstream::trunc); + fileGuard << confStr << std::flush; + fileGuard.close(); + + blet::Dict conf = blet::conf::loadFile(testFile); + EXPECT_EQ(conf["section"]["test"], 42); +} diff --git a/test/loadString.cpp b/test/loadString.cpp index 31df628..9e1b75b 100644 --- a/test/loadString.cpp +++ b/test/loadString.cpp @@ -24,6 +24,28 @@ GTEST_TEST(loadString, backSlash) { EXPECT_EQ(conf["test"]["test\\\"test"], 42); } +GTEST_TEST(loadString, keyOfMap) { + // clang-format off + const char confStr[] = "" + "[test]\n" + "test [ test ] = 42"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_EQ(conf["test"]["test"]["test"], 42); +} + +GTEST_TEST(loadString, keyOfMultiMap) { + // clang-format off + const char confStr[] = "" + "[test]\n" + "test [ test ][ test ] = 42"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_EQ(conf["test"]["test"]["test"]["test"], 42); +} + GTEST_TEST(loadString, quoteInKeyOfMap) { // clang-format off const char confStr[] = "" @@ -54,6 +76,25 @@ GTEST_TEST(loadString, empty) { } } +GTEST_TEST(loadString, nullValue) { + // clang-format off + const char confStr[] = "" + "[test]\n" + "test1 = \n" + "test2 = Null\n" + "test3 = null\n" + "test4 = None\n" + "test5 = none"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_TRUE(conf["test"]["test1"].isNull()); + EXPECT_TRUE(conf["test"]["test2"].isNull()); + EXPECT_TRUE(conf["test"]["test3"].isNull()); + EXPECT_TRUE(conf["test"]["test4"].isNull()); + EXPECT_TRUE(conf["test"]["test5"].isNull()); +} + GTEST_TEST(loadString, nullObject) { // clang-format off const char confStr[] = "" @@ -98,12 +139,14 @@ GTEST_TEST(loadString, multiSectionSubLevel) { "test = 42\n" "[[[test2]]]\n" "[[[[test3]]]]\n" + "test = 42\n" "[[test4]]\n" "test = 42"; // clang-format on const blet::Dict conf = blet::conf::loadString(confStr); EXPECT_EQ(conf["test"]["test"]["test"], 42); + EXPECT_EQ(conf["test"]["test"]["test2"]["test3"]["test"], 42); EXPECT_EQ(conf["test"]["test4"]["test"], 42); } @@ -349,3 +392,38 @@ GTEST_TEST(loadString, parseReplaceEscapeChar) { const blet::Dict conf = blet::conf::loadString(confStr); EXPECT_EQ(conf["test"]["1"], "\a\b\f\n\r\t\v\'\"\\"); } + +GTEST_TEST(loadString, parseJson) { + // clang-format off + const char confStr[] = "" + "{\n" + " \"test\": 42\n" + "}\n"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_EQ(conf["test"], 42); +} + +GTEST_TEST(loadString, parseJsonOneLine) { + // clang-format off + const char confStr[] = "{\"test1\":42,\"test2\":true}"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_EQ(conf["test1"], 42); + EXPECT_EQ(conf["test2"], true); +} + +GTEST_TEST(loadString, parseJsonWithSection) { + // clang-format off + const char confStr[] = "" + "[test]" + "{\n" + " \"test\": 42\n" + "}\n"; + // clang-format on + + const blet::Dict conf = blet::conf::loadString(confStr); + EXPECT_EQ(conf["test"]["test"], 42); +}