diff --git a/CMakeLists.txt b/CMakeLists.txt index 780388fa..68fd018b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++20") add_compile_options(/utf-8) ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS) else() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pthread -g -std=c++20") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pthread -g") set(CMAKE_CXX_FLAGS_RELEASE "-O3") endif(MSVC) set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl") @@ -79,14 +79,11 @@ add_executable(xml_example ${XML_EXAMPLE}) add_executable(test_xml ${TEST_XML}) add_executable(xml_benchmark ${XMLBENCH}) add_executable(test_xml_nothrow ${TEST_XMLNOTHROW}) -if(CMAKE_CXX_STANDARD GREATER_EQUAL 20) - add_executable(yaml_example ${YAML_EXAMPLE}) - add_executable(test_yaml ${TEST_YAML}) - add_executable(yaml_benchmark ${YAMLBENCH}) - add_executable(test_nothrow ${TEST_NOTHROW}) - add_executable(test_util ${TEST_UTIL}) - -endif() +add_executable(yaml_example ${YAML_EXAMPLE}) +add_executable(test_yaml ${TEST_YAML}) +add_executable(yaml_benchmark ${YAMLBENCH}) +add_executable(test_nothrow ${TEST_NOTHROW}) +add_executable(test_util ${TEST_UTIL}) # unit test option(BUILD_UNIT_TESTS "Build unit tests" ON) @@ -107,10 +104,8 @@ endif() add_test(NAME test_some COMMAND test_some) add_test(NAME test_ut COMMAND test_ut) add_test(NAME test_json_files COMMAND test_json_files) -if(CMAKE_CXX_STANDARD GREATER_EQUAL 20) - add_test(NAME test_xml COMMAND test_xml) - add_test(NAME test_yaml COMMAND test_yaml) - add_test(NAME test_nothrow COMMAND test_nothrow) - add_test(NAME test_util COMMAND test_util) - add_test(NAME test_xml_nothrow COMMAND test_xml_nothrow) -endif() \ No newline at end of file +add_test(NAME test_xml COMMAND test_xml) +add_test(NAME test_yaml COMMAND test_yaml) +add_test(NAME test_nothrow COMMAND test_nothrow) +add_test(NAME test_util COMMAND test_util) +add_test(NAME test_xml_nothrow COMMAND test_xml_nothrow) \ No newline at end of file diff --git a/README.md b/README.md index 255d7636..0713c4d9 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,57 @@ We can slove the problem1 easily with c++17: //now you can operate the file ``` +### how to handle the enum type as strings? + +By default, Iguana handle enum type as number type during serialization and deserialization. +To handle the enum type as strings during serialization and deserialization with Iguana, we need to define a full specialization template in the "iguana" namespace. This template is a struct that contains an array with the underlying numbers corresponding to the enum type. +For example, if we have the following enum type: + +```c++ +enum class Status { STOP = 10, START }; +``` + +And we want to handle the enum type as strings when parsing JSON: + +```c++ + std::string str = R"( +{ + "a": "START", + "b": "STOP", +} + )"; +``` + +To do this, we define the full specialization template in the "iguana" namespace: + +```c++ +namespace iguana { +template <> struct enum_value { + constexpr static std::array value = {10, 11}; +}; +} // namespace iguana +``` + +Once this is done, we can continue writing the rest of the code as usual. + +```c++ +struct enum_t { + Status a; + Status b; +}; +REFLECTION(enum_t, a, b); + +// deserialization +enum_t e; +iguana::from_json(e, str); +// serialization +enum_t e; +e.a = Status::START; +e.b = Status::STOP; +std::string ss; +iguana::to_json(e ss); +``` + ### Full sources: diff --git a/example/yaml_example.cpp b/example/yaml_example.cpp index 6e7935a9..15c93129 100644 --- a/example/yaml_example.cpp +++ b/example/yaml_example.cpp @@ -229,7 +229,7 @@ void library_example() { libraries: - name: Central Library - location: "Main\tStreet" + location: "Main\tStreet" #this is a comment books: - title: categories: diff --git a/iguana/util.hpp b/iguana/util.hpp index e3f8cf5c..247fd3d3 100644 --- a/iguana/util.hpp +++ b/iguana/util.hpp @@ -3,6 +3,7 @@ #include "define.h" #include "detail/charconv.h" #include "enum_reflection.hpp" +#include "error_code.h" #include "reflection.hpp" #include diff --git a/iguana/yaml_reader.hpp b/iguana/yaml_reader.hpp index 76c073d1..1c974335 100644 --- a/iguana/yaml_reader.hpp +++ b/iguana/yaml_reader.hpp @@ -7,82 +7,87 @@ namespace iguana { -template +template , int> = 0> IGUANA_INLINE void from_yaml(T &value, It &&it, It &&end, size_t min_spaces = 0); namespace detail { -template +template , int> = 0> IGUANA_INLINE void parse_escape_str(U &value, It &&it, It &&end) { auto start = it; while (it != end) { - if (*(it++) == '\\') [[unlikely]] { - if (*it == 'u') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - ++it; - if (std::distance(it, end) < 4) [[unlikely]] + if (*(it++) == '\\') + IGUANA_UNLIKELY { + if (*it == 'u') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + ++it; + if (std::distance(it, end) < 4) + IGUANA_UNLIKELY throw std::runtime_error(R"(Expected 4 hexadecimal digits)"); - auto code_point = parse_unicode_hex4(it); - encode_utf8(value, code_point); - start = it; - } else if (*it == 'n') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - start = ++it; - value.push_back('\n'); - } else if (*it == 't') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - start = ++it; - value.push_back('\t'); - } else if (*it == 'r') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - start = ++it; - value.push_back('\r'); - } else if (*it == 'b') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - start = ++it; - value.push_back('\b'); - } else if (*it == 'f') { - value.append(&*start, - static_cast(std::distance(start, it) - 1)); - start = ++it; - value.push_back('\f'); + auto code_point = parse_unicode_hex4(it); + encode_utf8(value, code_point); + start = it; + } else if (*it == 'n') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + start = ++it; + value.push_back('\n'); + } else if (*it == 't') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + start = ++it; + value.push_back('\t'); + } else if (*it == 'r') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + start = ++it; + value.push_back('\r'); + } else if (*it == 'b') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + start = ++it; + value.push_back('\b'); + } else if (*it == 'f') { + value.append(&*start, + static_cast(std::distance(start, it) - 1)); + start = ++it; + value.push_back('\f'); + } } - } } value.append(&*start, static_cast(std::distance(start, end))); } // use '-' here to simply represent '>-' -template +template , int> = 0> IGUANA_INLINE void parse_block_str(U &value, It &&it, It &&end, size_t min_spaces) { auto spaces = skip_space_and_lines(it, end, min_spaces); - if (spaces < min_spaces) [[unlikely]] { - // back to the end of the previous line - if constexpr (block_type == '|' || block_type == '>') { - value.push_back('\n'); + if (spaces < min_spaces) + IGUANA_UNLIKELY { + // back to the end of the previous line + if constexpr (block_type == '|' || block_type == '>') { + value.push_back('\n'); + } + it -= spaces + 1; + return; } - it -= spaces + 1; - return; - } while (it != end) { auto start = it; auto value_end = skip_till_newline(it, end); value.append(&*start, static_cast(std::distance(start, value_end))); spaces = skip_space_and_lines(it, end, min_spaces); - if ((spaces < min_spaces) || (it == end)) [[unlikely]] { - if constexpr (block_type == '|' || block_type == '>') { - value.push_back('\n'); + if ((spaces < min_spaces) || (it == end)) + IGUANA_UNLIKELY { + if constexpr (block_type == '|' || block_type == '>') { + value.push_back('\n'); + } + it -= spaces + 1; + return; } - it -= spaces + 1; - return; - } if constexpr (block_type == '|') { value.push_back('\n'); } else if constexpr (block_type == '-' || block_type == '>') { @@ -91,23 +96,25 @@ IGUANA_INLINE void parse_block_str(U &value, It &&it, It &&end, } } -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces); -template +template , int> = 0> IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { - if (value_begin == value_end) [[unlikely]] { - return; - } + if (value_begin == value_end) + IGUANA_UNLIKELY { return; } auto size = std::distance(value_begin, value_end); const auto start = &*value_begin; auto [p, ec] = detail::from_chars(start, start + size, value); - if (ec != std::errc{}) [[unlikely]] - throw std::runtime_error("Failed to parse number"); + if (ec != std::errc{}) + IGUANA_UNLIKELY + throw std::runtime_error("Failed to parse number"); } // string_view should be used for string with ' " ? -template +template , int> = 0> IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { using T = std::decay_t; auto start = value_begin; @@ -115,39 +122,59 @@ IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { if (*value_begin == '"') { ++start; --end; - if (*end != '"') [[unlikely]] { - throw std::runtime_error(R"(Expected ")"); - } - if constexpr (str_t) { + if (*end != '"') + IGUANA_UNLIKELY { + // TODO: improve + auto it = start; + while (*it != '"' && it != end) { + ++it; + } + if (it == end || (*(it + 1) != '#')) + IGUANA_UNLIKELY { throw std::runtime_error(R"(Expected ")"); } + end = it; + } + if constexpr (string_v) { parse_escape_str(value, start, end); return; } } else if (*value_begin == '\'') { ++start; --end; - if (*end != '\'') [[unlikely]] { - throw std::runtime_error(R"(Expected ')"); - } + if (*end != '\'') + IGUANA_UNLIKELY { throw std::runtime_error(R"(Expected ')"); } } value = T(&*start, static_cast(std::distance(start, end))); } -template +template , int> = 0> IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { if (static_cast(std::distance(value_begin, value_end)) != 1) - [[unlikely]] { - throw std::runtime_error("Expected one character"); - } + IGUANA_UNLIKELY { throw std::runtime_error("Expected one character"); } value = *value_begin; } -template +template , int> = 0> IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { - using T = std::underlying_type_t>; - parse_value(reinterpret_cast(value), value_begin, value_end); + static constexpr auto str_to_enum = get_enum_map>(); + if constexpr (bool_v) { + // not defined a specialization template + using T = std::underlying_type_t>; + parse_value(reinterpret_cast(value), value_begin, value_end); + } else { + auto enum_names = std::string_view( + &*value_begin, + static_cast(std::distance(value_begin, value_end))); + auto it = str_to_enum.find(enum_names); + if (it != str_to_enum.end()) + IGUANA_LIKELY { value = it->second; } + else { + throw std::runtime_error(std::string(enum_names) + + " missing corresponding value in enum_value"); + } + } } -template +template , int> = 0> IGUANA_INLINE void parse_value(U &&value, It &&value_begin, It &&value_end) { auto bool_v = std::string_view( &*value_begin, @@ -156,23 +183,21 @@ IGUANA_INLINE void parse_value(U &&value, It &&value_begin, It &&value_end) { value = true; } else if (bool_v == "false") { value = false; - } else [[unlikely]] { - throw std::runtime_error("Expected true or false"); - } + } else + IGUANA_UNLIKELY { throw std::runtime_error("Expected true or false"); } } -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { from_yaml(value, it, end, min_spaces); } -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { using T = std::decay_t; - if constexpr (str_t) { - if (skip_space_till_end(it, end)) [[unlikely]] { - return; - } + if constexpr (string_v) { + if (skip_space_till_end(it, end)) + IGUANA_UNLIKELY { return; } if (*it == '|') { ++it; parse_block_str<'|'>(value, it, end, min_spaces); @@ -198,14 +223,15 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { } } -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces); -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces); // minspaces : The minimum indentation -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { value.clear(); auto spaces = skip_space_and_lines(it, end, min_spaces); @@ -214,42 +240,43 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { ++it; skip_space_and_lines(it, end, min_spaces); while (it != end) { - if (*it == ']') [[unlikely]] { - ++it; - return; - } - if constexpr (plain_t) { + if (*it == ']') + IGUANA_UNLIKELY { + ++it; + return; + } + if constexpr (plain_v) { auto start = it; auto value_end = skip_till<',', ']'>(it, end); parse_value(value.emplace_back(), start, value_end); - if (*(it - 1) == ']') [[unlikely]] { - return; - } + if (*(it - 1) == ']') + IGUANA_UNLIKELY { return; } } else { parse_item(value.emplace_back(), it, end, spaces + 1); skip_space_and_lines(it, end, min_spaces); - if (*it == ',') [[likely]] { - ++it; - } + if (*it == ',') + IGUANA_LIKELY { ++it; } } skip_space_and_lines(it, end, min_spaces); } } else if (*it == '-') { while (it != end) { auto subspaces = skip_space_and_lines(it, end, spaces); - if (subspaces < spaces) [[unlikely]] { - it -= subspaces + 1; - return; - } - if (it == end) [[unlikely]] - return; + if (subspaces < spaces) + IGUANA_UNLIKELY { + it -= subspaces + 1; + return; + } + if (it == end) + IGUANA_UNLIKELY + return; match<'-'>(it, end); // space + 1 because of the skipped '-' subspaces = skip_space_and_lines(it, end, spaces + 1); - if constexpr (str_t) { - // except str_t because of scalar blocks + if constexpr (string_v) { + // except string_v because of scalar blocks parse_item(value.emplace_back(), it, end, spaces + 1); - } else if constexpr (plain_t) { + } else if constexpr (plain_v) { auto start = it; auto value_end = skip_till_newline(it, end); parse_value(value.emplace_back(), start, value_end); @@ -257,12 +284,11 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { parse_item(value.emplace_back(), it, end, subspaces); } } - } else [[unlikely]] { - throw std::runtime_error("Expected ']' or '-'"); - } + } else + IGUANA_UNLIKELY { throw std::runtime_error("Expected ']' or '-'"); } } -template +template , int> = 0> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { auto spaces = skip_space_and_lines(it, end, min_spaces); if (*it == '[') { @@ -271,7 +297,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { for_each(value, [&](auto &v, auto i) IGUANA__INLINE_LAMBDA { using value_type = std::decay_t; skip_space_and_lines(it, end, min_spaces); - if constexpr (plain_t) { + if constexpr (plain_v) { auto start = it; auto value_end = skip_till<',', ']'>(it, end); parse_value(v, start, value_end); @@ -290,9 +316,9 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { skip_space_and_lines(it, end, spaces); match<'-'>(it, end); auto subspaces = skip_space_and_lines(it, end, spaces + 1); - if constexpr (str_t) { + if constexpr (string_v) { parse_item(v, it, end, spaces + 1); - } else if constexpr (plain_t) { + } else if constexpr (plain_v) { auto start = it; auto value_end = skip_till_newline(it, end); parse_value(v, start, value_end); @@ -305,7 +331,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { } } -template +template , int>> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { using T = std::remove_reference_t; using key_type = typename T::key_type; @@ -315,44 +341,44 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { ++it; auto subspaces = skip_space_and_lines(it, end, min_spaces); while (it != end) { - if (*it == '}') [[unlikely]] { - ++it; - return; - } + if (*it == '}') + IGUANA_UNLIKELY { + ++it; + return; + } auto start = it; auto value_end = skip_till<':'>(it, end); key_type key; parse_value(key, start, value_end); subspaces = skip_space_and_lines(it, end, min_spaces); - if constexpr (plain_t) { + if constexpr (plain_v) { start = it; value_end = skip_till<',', '}'>(it, end); parse_value(value[key], start, value_end); - if (*(it - 1) == '}') [[unlikely]] { - return; - } + if (*(it - 1) == '}') + IGUANA_UNLIKELY { return; } } else { parse_item(value[key], it, end, min_spaces); subspaces = skip_space_and_lines(it, end, min_spaces); - if (*it == ',') [[likely]] { - ++it; - } + if (*it == ',') + IGUANA_LIKELY { ++it; } } subspaces = skip_space_and_lines(it, end, min_spaces); } } else { auto subspaces = skip_space_and_lines(it, end, min_spaces); while (it != end) { - if (subspaces < min_spaces) [[unlikely]] { - it -= subspaces + 1; - return; - } + if (subspaces < min_spaces) + IGUANA_UNLIKELY { + it -= subspaces + 1; + return; + } auto start = it; auto value_end = skip_till<':'>(it, end); key_type key; parse_value(key, start, value_end); subspaces = skip_space_and_lines(it, end, min_spaces); - if constexpr (plain_t && !str_t) { + if constexpr (plain_v && !string_v) { start = it; value_end = skip_till_newline(it, end); parse_value(value[key], start, value_end); @@ -364,27 +390,27 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { } } -template +template , int>> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { using T = std::remove_reference_t; value = std::make_unique(); - static_assert(!str_t, + static_assert(!string_v, "unique_ptr is not allowed"); parse_item(*value, it, end, min_spaces); } -template +template , int>> IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { using T = std::remove_reference_t; using value_type = typename T::value_type; auto spaces = skip_space_and_lines(it, end, min_spaces); - if (it == end) [[unlikely]] { - return; - } - if (spaces < min_spaces) [[unlikely]] { - it -= spaces + 1; - return; - } + if (it == end) + IGUANA_UNLIKELY { return; } + if (spaces < min_spaces) + IGUANA_UNLIKELY { + it -= spaces + 1; + return; + } auto start = it; std::decay_t value_end = end; if (*it == 'n') { @@ -395,19 +421,21 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, size_t min_spaces) { return; } } - if constexpr (str_t || !plain_t) { + if constexpr (string_v || !plain_v) { it = start; parse_item(value.emplace(), it, end, min_spaces); } else { - if (value_end == end) [[likely]] { - // if value_end isn't touched - value_end = skip_till_newline(it, end); - } + if (value_end == end) + IGUANA_LIKELY { + // if value_end isn't touched + value_end = skip_till_newline(it, end); + } parse_value(value.emplace(), start, value_end); } } -IGUANA_INLINE void skip_object_value(auto &&it, auto &&end, size_t min_spaces) { +template +IGUANA_INLINE void skip_object_value(It &&it, It &&end, size_t min_spaces) { int subspace = min_spaces; while (it != end) { while (it != end && *it != '\n') { @@ -415,9 +443,8 @@ IGUANA_INLINE void skip_object_value(auto &&it, auto &&end, size_t min_spaces) { } // here : it == '\n' subspace = 0; - if (it != end) [[likely]] { - ++it; - } + if (it != end) + IGUANA_LIKELY { ++it; } while (it != end && *(it++) == ' ') { ++subspace; } @@ -430,7 +457,7 @@ IGUANA_INLINE void skip_object_value(auto &&it, auto &&end, size_t min_spaces) { } // namespace detail -template +template , int>> IGUANA_INLINE void from_yaml(T &value, It &&it, It &&end, size_t min_spaces) { auto spaces = skip_space_and_lines(it, end, min_spaces); while (it != end) { @@ -441,34 +468,39 @@ IGUANA_INLINE void from_yaml(T &value, It &&it, It &&end, size_t min_spaces) { static constexpr auto frozen_map = get_iguana_struct_map(); if constexpr (frozen_map.size() > 0) { const auto &member_it = frozen_map.find(key); - if (member_it != frozen_map.end()) [[likely]] { - std::visit( - [&](auto &&member_ptr) IGUANA__INLINE_LAMBDA { - using V = std::decay_t; - if constexpr (std::is_member_pointer_v) { - detail::parse_item(value.*member_ptr, it, end, spaces + 1); - } else { - static_assert(!sizeof(V), "type not supported"); - } - }, - member_it->second); - } else [[unlikely]] { + if (member_it != frozen_map.end()) + IGUANA_LIKELY { + std::visit( + [&](auto &&member_ptr) IGUANA__INLINE_LAMBDA { + using V = std::decay_t; + if constexpr (std::is_member_pointer_v) { + detail::parse_item(value.*member_ptr, it, end, spaces + 1); + } else { + static_assert(!sizeof(V), "type not supported"); + } + }, + member_it->second); + } + else + IGUANA_UNLIKELY { #ifdef THROW_UNKNOWN_KEY - throw std::runtime_error("Unknown key: " + std::string(key)); + throw std::runtime_error("Unknown key: " + std::string(key)); #else - detail::skip_object_value(it, end, spaces + 1); + detail::skip_object_value(it, end, spaces + 1); #endif - } + } } auto subspaces = skip_space_and_lines(it, end, min_spaces); - if (subspaces < min_spaces) [[unlikely]] { - it -= subspaces + 1; - return; - } + if (subspaces < min_spaces) + IGUANA_UNLIKELY { + it -= subspaces + 1; + return; + } } } -template +template , int> = 0> IGUANA_INLINE void from_yaml(T &value, It &&it, It &&end) { detail::parse_item(value, it, end, 0); } @@ -484,12 +516,14 @@ IGUANA_INLINE void from_yaml(T &value, It &&it, It &&end, } } -template +template , int> = 0> IGUANA_INLINE void from_yaml(T &value, const View &view) { from_yaml(value, std::begin(view), std::end(view)); } -template +template , int> = 0> IGUANA_INLINE void from_yaml(T &value, const View &view, std::error_code &ec) noexcept { try { diff --git a/iguana/yaml_util.hpp b/iguana/yaml_util.hpp index ccf38718..632a6e17 100644 --- a/iguana/yaml_util.hpp +++ b/iguana/yaml_util.hpp @@ -1,201 +1,81 @@ #pragma once -#include "define.h" -#include "reflection.hpp" -#include "value.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "util.hpp" namespace iguana { - -template -concept char_t = std::same_as < std::decay_t, -char > || std::same_as, char16_t> || - std::same_as, char32_t> || - std::same_as, wchar_t>; - -template -concept bool_t = std::same_as < std::decay_t, -bool > || std::same_as, std::vector::reference>; - -template -concept int_t = - std::integral> && !char_t> && !bool_t; - -template -concept float_t = std::floating_point>; - -template -concept num_t = std::floating_point> || int_t; - -template -concept enum_t = std::is_enum_v>; - -template constexpr inline bool is_basic_string_view = false; - -template -constexpr inline bool is_basic_string_view> = true; - -template -concept str_view_t = is_basic_string_view>; - -template -concept str_t = - std::convertible_to, std::string_view> && !str_view_t; - -template -concept string_t = std::convertible_to, std::string_view>; - -template -concept tuple_t = is_tuple>::value; - -template -concept sequence_container_t = - is_sequence_container>::value; - -template -concept unique_ptr_t = requires(Type ptr) { - ptr.operator*(); - typename std::remove_cvref_t::element_type; -} -&&!requires(Type ptr, Type ptr2) { ptr = ptr2; }; - -template -concept optional_t = requires(Type optional) { - optional.value(); - optional.has_value(); - optional.operator*(); - typename std::remove_cvref_t::value_type; -}; - -template -concept container = requires(Type container) { - typename std::remove_cvref_t::value_type; - container.size(); - container.begin(); - container.end(); -}; -template -concept map_container = container && requires(Type container) { - typename std::remove_cvref_t::mapped_type; -}; - -template -concept plain_t = - string_t || num_t || char_t || bool_t || enum_t; - -template -concept c_array = std::is_array_v> && - std::extent_v> > -0; - -template -concept array = requires(Type arr) { - arr.size(); - std::tuple_size>{}; -}; - -template -concept tuple = !array && requires(Type tuple) { - std::get<0>(tuple); - sizeof(std::tuple_size>); -}; - -template -concept refletable = is_reflection_v>; - -// TODO: support c_array -template -concept non_refletable = container || c_array || tuple || - optional_t || unique_ptr_t || std::is_fundamental_v; - -IGUANA_INLINE void skip_yaml_comment(auto &&it, auto &&end) { - while (++it != end && *it != '\n') - ; -} - -// match c and skip -template IGUANA_INLINE void match(auto &&it, auto &&end) { - if (it == end || *it != c) [[unlikely]] { - static constexpr char b[] = {c, '\0'}; - std::string error = std::string("Expected:").append(b); - throw std::runtime_error(error); - } else [[likely]] { - ++it; - } -} - // return true when it==end -IGUANA_INLINE bool skip_space_till_end(auto &&it, auto &&end) { - while (it != end && *it == ' ') +template +IGUANA_INLINE bool skip_space_till_end(It &&it, It &&end) { + while (it != end && *it < 33) ++it; return it == end; } // will not skip '\n' -IGUANA_INLINE auto skip_till_newline(auto &&it, auto &&end) { - if (it == end) [[unlikely]] { - return it; - } +template IGUANA_INLINE auto skip_till_newline(It &&it, It &&end) { + if (it == end) + IGUANA_UNLIKELY { return it; } std::decay_t res = it; while ((it != end) && (*it != '\n')) { - if (*it == ' ') [[unlikely]] { - res = it; - while (it != end && *it == ' ') - ++it; - } else if (*it == '#') [[unlikely]] { - if (*(it - 1) == ' ') [[unlikely]] { - // it - 1 should be legal because this func is for parse value - while ((it != end) && *it != '\n') { + if (*it == ' ') + IGUANA_UNLIKELY { + res = it; + while (it != end && *it == ' ') + ++it; + } + else if (*it == '#') + IGUANA_UNLIKELY { + if (*(it - 1) == ' ') { + // it - 1 should be legal because this func is for parse value + while ((it != end) && *it != '\n') { + ++it; + } + return res; + } else { ++it; } - return res; } - } else [[likely]] { - ++it; - } + else + IGUANA_LIKELY { ++it; } } return (*(it - 1) == ' ') ? res : it; } -template IGUANA_INLINE auto skip_till(auto &&it, auto &&end) { - if (it == end) [[unlikely]] { - return it; - } +template +IGUANA_INLINE auto skip_till(It &&it, It &&end) { + if (it == end) + IGUANA_UNLIKELY { return it; } std::decay_t res = it; while ((it != end) && (!((... || (*it == C))))) { - if (*it == '\n') [[unlikely]] { - throw std::runtime_error("\\n is not expected"); - } else if (*it == ' ') [[unlikely]] { - res = it; - while (it != end && *it == ' ') - ++it; - } else if (*it == '#') [[unlikely]] { - if (*(it - 1) == ' ') [[unlikely]] { - // it - 1 may be illegal - while ((it != end) && *it != '\n') { + if (*it == '\n') + IGUANA_UNLIKELY { throw std::runtime_error("\\n is not expected"); } + else if (*it == ' ') + IGUANA_UNLIKELY { + res = it; + while (it != end && *it == ' ') ++it; - } - return res; } - } else [[likely]] { - ++it; - } + else if (*it == '#') + IGUANA_UNLIKELY { + if (*(it - 1) == ' ') + IGUANA_UNLIKELY { + // it - 1 may be illegal + while ((it != end) && *it != '\n') { + ++it; + } + return res; + } + } + else + IGUANA_LIKELY { ++it; } } - if (it == end) [[unlikely]] { - static constexpr char b[] = {C..., '\0'}; - std::string error = std::string("Expected one of these: ").append(b); - throw std::runtime_error(error); - } + if (it == end) + IGUANA_UNLIKELY { + static constexpr char b[] = {C..., '\0'}; + std::string error = std::string("Expected one of these: ").append(b); + throw std::runtime_error(error); + } ++it; // skip return (*(it - 2) == ' ') ? res : it - 1; } @@ -203,9 +83,8 @@ template IGUANA_INLINE auto skip_till(auto &&it, auto &&end) { // If there are '\n' , return indentation // If not, return minspaces + space // If Throw == true, check res < minspaces -template -IGUANA_INLINE size_t skip_space_and_lines(auto &&it, auto &&end, - size_t minspaces) { +template +IGUANA_INLINE size_t skip_space_and_lines(It &&it, It &&end, size_t minspaces) { size_t res = minspaces; while (it != end) { if (*it == '\n') { @@ -213,15 +92,16 @@ IGUANA_INLINE size_t skip_space_and_lines(auto &&it, auto &&end, res = 0; auto start = it; // skip the --- line - if ((it != end) && (*it == '-')) [[unlikely]] { - auto line_end = skip_till(it, end); - auto line = std::string_view( - &*start, static_cast(std::distance(start, line_end))); - if (line != "---") { - it = start; + if ((it != end) && (*it == '-')) + IGUANA_UNLIKELY { + auto line_end = skip_till(it, end); + auto line = std::string_view( + &*start, static_cast(std::distance(start, line_end))); + if (line != "---") { + it = start; + } } - } - } else if (*it == ' ') { + } else if (*it == ' ' || *it == '\t') { ++it; ++res; } else if (*it == '#') { @@ -231,9 +111,8 @@ IGUANA_INLINE size_t skip_space_and_lines(auto &&it, auto &&end, res = 0; } else { if constexpr (Throw) { - if (res < minspaces) [[unlikely]] { - throw std::runtime_error("Indentation problem"); - } + if (res < minspaces) + IGUANA_UNLIKELY { throw std::runtime_error("Indentation problem"); } } return res; } diff --git a/iguana/yaml_writer.hpp b/iguana/yaml_writer.hpp index 96fe4942..753ae76b 100644 --- a/iguana/yaml_writer.hpp +++ b/iguana/yaml_writer.hpp @@ -5,24 +5,28 @@ namespace iguana { -template +template , int> = 0> IGUANA_INLINE void to_yaml(T &&t, Stream &s, size_t min_spaces = 0); -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, T &&t, size_t min_spaces) { ss.push_back('\n'); to_yaml(std::forward(t), ss, min_spaces); } // TODO: support more string style, support escape -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, T &&t, size_t min_spaces) { ss.append(t.data(), t.size()); if constexpr (appendLf) ss.push_back('\n'); } -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, T value, size_t min_spaces) { char temp[65]; auto p = detail::to_chars(temp, value); @@ -47,21 +51,40 @@ IGUANA_INLINE void render_yaml_value(Stream &ss, bool value, ss.push_back('\n'); } -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, T value, size_t min_spaces) { - render_yaml_value(ss, static_cast>(value), - min_spaces); + static constexpr auto enum_to_str = get_enum_map>(); + if constexpr (bool_v) { + render_yaml_value(ss, static_cast>(value), + min_spaces); + } else { + auto it = enum_to_str.find(value); + if (it != enum_to_str.end()) + IGUANA_LIKELY { + auto str = it->second; + render_yaml_value(ss, std::string_view(str.data(), str.size()), + min_spaces); + } + else { + throw std::runtime_error( + std::to_string(static_cast>(value)) + + " is a missing value in enum_value"); + } + } } -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &val, size_t min_spaces); -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &val, size_t min_spaces); -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &t, size_t min_spaces) { ss.push_back('\n'); @@ -72,7 +95,7 @@ IGUANA_INLINE void render_yaml_value(Stream &ss, const T &t, } } -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, T &&t, size_t min_spaces) { ss.push_back('\n'); for_each(std::forward(t), @@ -83,7 +106,8 @@ IGUANA_INLINE void render_yaml_value(Stream &ss, T &&t, size_t min_spaces) { }); } -template +template , int> = 0> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &t, size_t min_spaces) { ss.push_back('\n'); @@ -95,7 +119,7 @@ IGUANA_INLINE void render_yaml_value(Stream &ss, const T &t, } } -template +template , int>> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &val, size_t min_spaces) { if (!val) { @@ -106,7 +130,7 @@ IGUANA_INLINE void render_yaml_value(Stream &ss, const T &val, } } -template +template , int>> IGUANA_INLINE void render_yaml_value(Stream &ss, const T &val, size_t min_spaces) { if (!val) { @@ -122,7 +146,7 @@ constexpr auto write_yaml_key = [](auto &s, auto i, s.append(name.data(), name.size()); }; -template +template , int>> IGUANA_INLINE void to_yaml(T &&t, Stream &s, size_t min_spaces) { for_each(std::forward(t), [&t, &s, min_spaces](const auto &v, auto i) IGUANA__INLINE_LAMBDA { @@ -142,10 +166,11 @@ IGUANA_INLINE void to_yaml(T &&t, Stream &s, size_t min_spaces) { }); } -template +template , int> = 0> IGUANA_INLINE void to_yaml(T &&t, Stream &s) { - if constexpr (tuple_t || map_container || sequence_container_t || - optional_t || unique_ptr_t) + if constexpr (tuple_v || map_container_v || sequence_container_v || + optional_v || unique_ptr_v) render_yaml_value(s, std::forward(t), 0); else static_assert(!sizeof(T), "don't suppport this type"); diff --git a/test/test_yaml.cpp b/test/test_yaml.cpp index 1df25820..6c4dedac 100644 --- a/test/test_yaml.cpp +++ b/test/test_yaml.cpp @@ -667,7 +667,7 @@ TEST_CASE("test example libraries") { libraries: - name: Central Library - location: "Main\tStreet" + location: "Main\tStreet"#this is a comment books: - title: categories: @@ -765,6 +765,103 @@ TEST_CASE("test_tuple_example") { iguana::from_yaml(tuple2, ss); validator(tuple2); } + +enum class Fruit { + APPLE = 9999, + BANANA = -4, + ORANGE = 10, + MANGO = 99, + CHERRY = 7, + GRAPE = 100000 +}; +enum class Color { + BULE = 10, + RED = 15, +}; +enum class Status { stop = 10, start }; +namespace iguana { +template <> struct enum_value { + constexpr static std::array value = {9999, -4, 10, 99, 7, 100000}; +}; + +} // namespace iguana +struct test_enum_t { + Fruit a; + Fruit b; + Fruit c; + Fruit d; + Fruit e; + Fruit f; + Color g; + Color h; +}; +REFLECTION(test_enum_t, a, b, c, d, e, f, g, h); + +TEST_CASE("test enum") { + auto validator = [](test_enum_t e) { + CHECK(e.a == Fruit::APPLE); + CHECK(e.b == Fruit::BANANA); + CHECK(e.c == Fruit::ORANGE); + CHECK(e.d == Fruit::MANGO); + CHECK(e.e == Fruit::CHERRY); + CHECK(e.f == Fruit::GRAPE); + CHECK(e.g == Color::BULE); + CHECK(e.h == Color::RED); + }; + std::string str = R"( +--- +a: APPLE +b: BANANA +c: ORANGE +d: MANGO +e: CHERRY +f: GRAPE +g: 10 +h: 15 + )"; + test_enum_t e; + iguana::from_yaml(e, str); + validator(e); + + std::string ss; + iguana::to_yaml(e, ss); + std::cout << ss << std::endl; + test_enum_t e1; + iguana::from_yaml(e1, ss); + validator(e1); +} + +enum class State { STOP = 10, START }; +namespace iguana { +template <> struct enum_value { + constexpr static std::array value = {10}; +}; +} // namespace iguana + +struct enum_exception_t { + State a; + State b; +}; +REFLECTION(enum_exception_t, a, b); + +TEST_CASE("enum exception") { + std::string str = R"( +a: START +b: STOP + )"; + { + enum_exception_t e; + CHECK_THROWS(iguana::from_yaml(e, str)); + } + { + enum_exception_t e; + std::string ss; + e.a = State::START; + e.b = State::STOP; + CHECK_THROWS(iguana::to_yaml(e, ss)); + } +} + // doctest comments // 'function' : must be 'attribute' - see issue #182 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)