diff --git a/src/openvic-simulation/core/Typedefs.hpp b/src/openvic-simulation/core/Typedefs.hpp index b4c0cfa8e..9898a664f 100644 --- a/src/openvic-simulation/core/Typedefs.hpp +++ b/src/openvic-simulation/core/Typedefs.hpp @@ -116,6 +116,14 @@ namespace OpenVic { #endif } + template + constexpr bool is_constexpr(Lambda) { + return true; + } + constexpr bool is_constexpr(...) { + return false; + } + namespace cow { template T const& read(T const& v) { diff --git a/src/openvic-simulation/core/memory/Formatting.hpp b/src/openvic-simulation/core/memory/Formatting.hpp index 2e229ee18..cd95aab24 100644 --- a/src/openvic-simulation/core/memory/Formatting.hpp +++ b/src/openvic-simulation/core/memory/Formatting.hpp @@ -12,15 +12,9 @@ namespace OpenVic::memory { namespace fmt { - template - using basic_memory_buffer = ::fmt::basic_memory_buffer< - T, - ::fmt::inline_buffer_size, - foonathan::memory::std_allocator< - T, - OpenVic::memory::tracker - > - >; + template + using basic_memory_buffer = + ::fmt::basic_memory_buffer>>; inline static memory::string vformat(::fmt::string_view fmt, ::fmt::format_args args) { memory::fmt::basic_memory_buffer buf {}; diff --git a/src/openvic-simulation/core/object/Date.cpp b/src/openvic-simulation/core/object/Date.cpp index 26a67da59..aebaac2d3 100644 --- a/src/openvic-simulation/core/object/Date.cpp +++ b/src/openvic-simulation/core/object/Date.cpp @@ -101,7 +101,7 @@ struct date_writer { } else if (upper >= 0 && upper < 100) { write2(static_cast(upper)); } else { - _out = detail::write(_out, upper); + _out = fmt::detail::write(_out, upper); } } @@ -118,11 +118,11 @@ struct date_writer { } void on_abbr_weekday() { - _out = detail::write(_out, _date.get_weekday_name().substr(0, 3)); + _out = fmt::detail::write(_out, _date.get_weekday_name().substr(0, 3)); } void on_full_weekday() { - _out = detail::write(_out, _date.get_weekday_name()); + _out = fmt::detail::write(_out, _date.get_weekday_name()); } void on_dec0_weekday(numeric_system ns) { @@ -135,10 +135,10 @@ struct date_writer { } void on_abbr_month() { - _out = detail::write(_out, _date.get_month_name().substr(0, 3)); + _out = fmt::detail::write(_out, _date.get_month_name().substr(0, 3)); } void on_full_month() { - _out = detail::write(_out, _date.get_month_name()); + _out = fmt::detail::write(_out, _date.get_month_name()); } void on_dec_month(numeric_system ns, pad_type pad) { @@ -177,24 +177,24 @@ struct date_writer { char buf[8]; write_digit2_separated( buf, // - detail::to_unsigned(_date.get_day()), // - detail::to_unsigned(_date.get_month()), // - detail::to_unsigned(split_year_lower(_date.get_year())), // + fmt::detail::to_unsigned(_date.get_day()), // + fmt::detail::to_unsigned(_date.get_month()), // + fmt::detail::to_unsigned(split_year_lower(_date.get_year())), // '/' ); - _out = detail::copy(std::begin(buf), std::end(buf), _out); + _out = fmt::detail::copy(std::begin(buf), std::end(buf), _out); } void on_us_date() { char buf[8]; write_digit2_separated( buf, // - detail::to_unsigned(_date.get_month()), // - detail::to_unsigned(_date.get_day()), // - detail::to_unsigned(split_year_lower(_date.get_year())), // + fmt::detail::to_unsigned(_date.get_month()), // + fmt::detail::to_unsigned(_date.get_day()), // + fmt::detail::to_unsigned(split_year_lower(_date.get_year())), // '/' ); - _out = detail::copy(std::begin(buf), std::end(buf), _out); + _out = fmt::detail::copy(std::begin(buf), std::end(buf), _out); } void on_iso_date() { @@ -202,7 +202,7 @@ struct date_writer { char buf[10]; size_t offset = 0; if (year >= 0 && year < 10000) { - detail::write2digits(buf, static_cast(year / 100)); + fmt::detail::write2digits(buf, static_cast(year / 100)); } else { offset = 4; write_year_extended(year, pad_type::zero); @@ -211,11 +211,11 @@ struct date_writer { write_digit2_separated( buf + 2, // static_cast(year % 100), // - detail::to_unsigned(_date.get_month()), // - detail::to_unsigned(_date.get_day()), // + fmt::detail::to_unsigned(_date.get_month()), // + fmt::detail::to_unsigned(_date.get_day()), // '-' ); - _out = detail::copy(std::begin(buf) + offset, std::end(buf), _out); + _out = fmt::detail::copy(std::begin(buf) + offset, std::end(buf), _out); } private: @@ -223,7 +223,7 @@ struct date_writer { if (pad == pad_type::none) { return out; } - return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); + return fmt::detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); } static OutputIt write_padding(OutputIt out, pad_type pad) { @@ -234,17 +234,17 @@ struct date_writer { } void write1(int value) { - *_out++ = static_cast('0' + detail::to_unsigned(value) % 10); + *_out++ = static_cast('0' + fmt::detail::to_unsigned(value) % 10); } void write2(int value) { - const char* d = detail::digits2(detail::to_unsigned(value) % 100); + const char* d = fmt::detail::digits2(fmt::detail::to_unsigned(value) % 100); *_out++ = *d++; *_out++ = *d; } void write2(int value, pad_type pad) { - unsigned int v = detail::to_unsigned(value) % 100; + unsigned int v = fmt::detail::to_unsigned(value) % 100; if (v >= 10) { - const char* d = detail::digits2(v); + const char* d = fmt::detail::digits2(v); *_out++ = *d++; *_out++ = *d; } else { @@ -261,8 +261,8 @@ struct date_writer { year = 0 - year; --width; } - detail::uint32_or_64_or_128_t n = detail::to_unsigned(year); - const int num_digits = detail::count_digits(n); + fmt::detail::uint32_or_64_or_128_t n = fmt::detail::to_unsigned(year); + const int num_digits = fmt::detail::count_digits(n); if (negative && pad == pad_type::zero) { *_out++ = '-'; } @@ -272,7 +272,7 @@ struct date_writer { if (negative && pad != pad_type::zero) { *_out++ = '-'; } - _out = detail::format_decimal(_out, n, num_digits); + _out = fmt::detail::format_decimal(_out, n, num_digits); } void write_year(long long year, pad_type pad) { write_year_extended(year, pad); @@ -308,7 +308,7 @@ struct date_writer { digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); constexpr const size_t len = 8; - if (detail::const_check(detail::is_big_endian())) { + if (fmt::detail::const_check(fmt::detail::is_big_endian())) { char tmp[len]; std::memcpy(tmp, &digits, len); std::reverse_copy(tmp, tmp + len, buf); @@ -364,5 +364,5 @@ fmt::format_context::iterator fmt::formatter::format(Date d, format_contex basic_appender out = basic_appender(buf); parse_date_format(_fmt.begin(), _fmt.end(), date_writer { out, d }); - return detail::write(ctx.out(), string_view { buf.data(), buf.size() }, specs); + return fmt::detail::write(ctx.out(), string_view { buf.data(), buf.size() }, specs); } diff --git a/src/openvic-simulation/core/string/StringLiteral.hpp b/src/openvic-simulation/core/string/StringLiteral.hpp index 8b5c1687e..d06479fee 100644 --- a/src/openvic-simulation/core/string/StringLiteral.hpp +++ b/src/openvic-simulation/core/string/StringLiteral.hpp @@ -296,4 +296,22 @@ namespace OpenVic { // Size of 1 to include null terminator template> string_literal() -> string_literal<1, CharT, Traits>; + + template + struct is_string_literal : std::false_type {}; + + template + struct is_string_literal> : std::true_type {}; + + template + struct is_string_literal> : std::true_type {}; + + template + struct is_string_literal const&> : std::true_type {}; + + template + inline constexpr bool is_string_literal_v = is_string_literal::value; + + template + concept string_literal_concept = is_string_literal_v; } diff --git a/src/openvic-simulation/core/template/Concepts.hpp b/src/openvic-simulation/core/template/Concepts.hpp index d21d92c12..609551985 100644 --- a/src/openvic-simulation/core/template/Concepts.hpp +++ b/src/openvic-simulation/core/template/Concepts.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -231,4 +232,25 @@ namespace OpenVic { concept strict_regular_invocable_r = std::regular_invocable && requires(F f, Args&&... args) { { f(static_cast(args)...) } -> std::same_as; }; -} \ No newline at end of file + + namespace detail { + template + constexpr bool enable_if_constexpr_constructible(int) { + return true; + } + + template + constexpr bool enable_if_constexpr_constructible(long) { + return false; + } + } + + template + struct is_constexpr_constructible : std::bool_constant(0)> {}; + + template + inline static constexpr bool is_constexpr_constructible_v = is_constexpr_constructible::value; + + template + concept constexpr_constructible = is_constexpr_constructible_v; +} diff --git a/src/openvic-simulation/dataloader/Vic2PathSearch.cpp b/src/openvic-simulation/dataloader/Vic2PathSearch.cpp index 3b169d6ea..412bbaff3 100644 --- a/src/openvic-simulation/dataloader/Vic2PathSearch.cpp +++ b/src/openvic-simulation/dataloader/Vic2PathSearch.cpp @@ -219,7 +219,7 @@ static fs::path _search_for_game_path(fs::path hint_path) { lexy_vdf::Parser parser; memory::string buffer; - auto error_log_stream = detail::make_callback_stream( + auto error_log_stream = ovdl::detail::make_callback_stream( [](void const* s, std::streamsize n, void* user_data) -> std::streamsize { if (s != nullptr && n > 0 && user_data != nullptr) { static_cast(user_data)->append(static_cast(s), n); diff --git a/src/openvic-simulation/definition/dataloader/ErrorMacros.hpp b/src/openvic-simulation/definition/dataloader/ErrorMacros.hpp new file mode 100644 index 000000000..70d9dd895 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/ErrorMacros.hpp @@ -0,0 +1,432 @@ +#pragma once + +#include "openvic-simulation/core/Typedefs.hpp" // IWYU pragma: keep +#include "openvic-simulation/definition/dataloader/Logger.hpp" // IWYU pragma: keep + +// Based heavily on https://github.com/godotengine/godot/blob/34d06658a85845111a50db9e485ec4a0701d4298/core/error/error_macros.h + +/** + * Dataloader Error macros. + * WARNING: These macros work in the opposite way to assert(). + * + * Unlike exceptions and asserts, these macros try to maintain consistency and stability. + * In most cases, bugs and/or invalid data are not fatal. They should never allow a perfectly + * running application to fail or crash. + * Always try to return processable data, so the engine can keep running well. + * Use the _MSG versions to print a meaningful message to help with debugging. + * + * The `((void)0)` no-op statement is used as a trick to force us to put a semicolon after + * those macros, making them look like proper statements. + * The if wrappers are used to ensure that the macro replacement does not trigger unexpected + * issues when expanded e.g. after an `if (cond) OV_DL_ERR_FAIL();` without braces. + */ + +// Index out of bounds error macros. +// These macros should be used instead of `OV_DL_ERR_FAIL_COND` for bounds checking. + +// Integer index out of bounds error macros. + +/** + * Try using `OV_DL_ERR_FAIL_INDEX_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, the current function returns. + */ +#define OV_DL_ERR_FAIL_INDEX(m_index, m_size) \ + if (OV_unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "Index {} = {} is out of bounds ({} = {}).", _OV_STR(m_index), (m_index), _OV_STR(m_size), (m_size) \ + ); \ + return; \ + } else \ + ((void)0) + +/** + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, prints `m_msg` and the current function returns. + */ +#define OV_DL_ERR_FAIL_INDEX_MSG(m_index, m_size, m_msg) \ + if (OV_unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tIndex {} = {} is out of bounds ({} = {}).", (m_msg), _OV_STR(m_index), (m_index), _OV_STR(m_size), (m_size) \ + ); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_INDEX_V_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_INDEX_V(m_index, m_size, m_retval) \ + if (OV_unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "Index {} = {} is out of bounds ({} = {}). Returning: {}", \ + _OV_STR(m_index), \ + (m_index), \ + _OV_STR(m_size), \ + (m_size), \ + _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, prints `m_msg` and the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \ + if (OV_unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tIndex {} = {} is out of bounds ({} = {}). Returning: {}", \ + (m_msg), \ + _OV_STR(m_index), \ + (m_index), \ + _OV_STR(m_size), \ + (m_size), \ + _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +// Unsigned integer index out of bounds error macros. + +/** + * Try using `OV_DL_ERR_FAIL_UNSIGNED_INDEX_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, the current function returns. + */ +#define OV_DL_ERR_FAIL_UNSIGNED_INDEX(m_index, m_size) \ + if (OV_unlikely((m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "Index {} = {} is out of bounds ({} = {}).", _OV_STR(m_index), (m_index), _OV_STR(m_size), (m_size) \ + ); \ + return; \ + } else \ + ((void)0) + +/** + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, prints `m_msg` and the current function returns. + */ +#define OV_DL_ERR_FAIL_UNSIGNED_INDEX_MSG(m_index, m_size, m_msg) \ + if (OV_unlikely((m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tIndex {} = {} is out of bounds ({} = {}).", (m_msg), _OV_STR(m_index), (m_index), _OV_STR(m_size), (m_size) \ + ); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_UNSIGNED_INDEX_V_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_UNSIGNED_INDEX_V(m_index, m_size, m_retval) \ + if (OV_unlikely((m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "Index {} = {} is out of bounds ({} = {}). Returning: {}", \ + _OV_STR(m_index), \ + (m_index), \ + _OV_STR(m_size), \ + (m_size), \ + _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, prints `m_msg` and the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_UNSIGNED_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \ + if (OV_unlikely((m_index) >= (m_size))) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tIndex {} = {} is out of bounds ({} = {}). Returning: {}", \ + (m_msg), \ + _OV_STR(m_index), \ + (m_index), \ + _OV_STR(m_size), \ + (m_size), \ + _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +// Null reference error macros. + +/** + * Try using `OV_DL_ERR_FAIL_NULL_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures a pointer `m_param` is not null. + * If it is null, the current function returns. + */ +#define OV_DL_ERR_FAIL_NULL(m_param) \ + if (OV_unlikely(m_param == nullptr)) { \ + ::OpenVic::dataloader::log::error("Parameter \"{}\" is null.", _OV_STR(m_param)); \ + return; \ + } else \ + ((void)0) + +/** + * Ensures a pointer `m_param` is not null. + * If it is null, prints `m_msg` and the current function returns. + */ +#define OV_DL_ERR_FAIL_NULL_MSG(m_param, m_msg) \ + if (OV_unlikely(m_param == nullptr)) { \ + ::OpenVic::dataloader::log::error("{}\n\tParameter \"{}\" is null.", (m_msg), _OV_STR(m_param)); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_NULL_V_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures a pointer `m_param` is not null. + * If it is null, the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_NULL_V(m_param, m_retval) \ + if (OV_unlikely(m_param == nullptr)) { \ + ::OpenVic::dataloader::log::error("Parameter \"{}\" is null. Returning: {}", _OV_STR(m_param), _OV_STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Ensures a pointer `m_param` is not null. + * If it is null, prints `m_msg` and the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_NULL_V_MSG(m_param, m_retval, m_msg) \ + if (OV_unlikely(m_param == nullptr)) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tParameter \"{}\" is null. Returning: {}", (m_msg), _OV_STR(m_param), _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_COND_MSG`. + * Only use this macro if there is no sensible error message. + * If checking for null use OV_DL_ERR_FAIL_NULL_MSG instead. + * If checking index bounds use OV_DL_ERR_FAIL_INDEX_MSG instead. + * + * Ensures `m_cond` is false. + * If `m_cond` is true, the current function returns. + */ +#define OV_DL_ERR_FAIL_COND(m_cond) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("Condition \"{}\" is true.", _OV_STR(m_param)); \ + return; \ + } else \ + ((void)0) + +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg` and the current function returns. + * + * If checking for null use OV_DL_ERR_FAIL_NULL_MSG instead. + * If checking index bounds use OV_DL_ERR_FAIL_INDEX_MSG instead. + */ +#define OV_DL_ERR_FAIL_COND_MSG(m_cond, m_msg) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("{}\n\tCondition \"{}\" is true.", (m_msg), _OV_STR(m_param)); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_COND_V_MSG`. + * Only use this macro if there is no sensible error message. + * If checking for null use OV_DL_ERR_FAIL_NULL_V_MSG instead. + * If checking index bounds use OV_DL_ERR_FAIL_INDEX_V_MSG instead. + * + * Ensures `m_cond` is false. + * If `m_cond` is true, the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_COND_V(m_cond, m_retval) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("Condition \"{}\" is true. Returning: {}", _OV_STR(m_param), _OV_STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg` and the current function returns `m_retval`. + * + * If checking for null use OV_DL_ERR_FAIL_NULL_V_MSG instead. + * If checking index bounds use OV_DL_ERR_FAIL_INDEX_V_MSG instead. + */ +#define OV_DL_ERR_FAIL_COND_V_MSG(m_cond, m_retval, m_msg) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error( \ + "{}\n\tCondition \"{}\" is true. Returning: {}", (m_msg), _OV_STR(m_param), _OV_STR(m_retval) \ + ); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_CONTINUE_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures `m_cond` is false. + * If `m_cond` is true, the current loop continues. + */ +#define OV_DL_ERR_CONTINUE(m_cond) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("Condition \"{}\" is true. Continuing.", _OV_STR(m_param)); \ + continue; \ + } else \ + ((void)0) + +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg` and the current loop continues. + */ +#define OV_DL_ERR_CONTINUE_MSG(m_cond, m_msg) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("{}\n\tCondition \"{}\" is true. Continuing.", (m_msg), _OV_STR(m_param)); \ + continue; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_BREAK_MSG`. + * Only use this macro if there is no sensible error message. + * + * Ensures `m_cond` is false. + * If `m_cond` is true, the current loop breaks. + */ +#define OV_DL_ERR_BREAK(m_cond) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("Condition \"{}\" is true. Breaking.", _OV_STR(m_param)); \ + break; \ + } else \ + ((void)0) + +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg` and the current loop breaks. + */ +#define OV_DL_ERR_BREAK_MSG(m_cond, m_msg) \ + if (OV_unlikely(m_cond)) { \ + ::OpenVic::dataloader::log::error("{}\n\tCondition \"{}\" is true. Breaking.", (m_msg), _OV_STR(m_param)); \ + break; \ + } else \ + ((void)0) + +// Generic error macros. + +/** + * Try using `OV_DL_ERR_FAIL_COND_MSG` or `OV_DL_ERR_FAIL_MSG`. + * Only use this macro if more complex error detection or recovery is required, and + * there is no sensible error message. + * + * The current function returns. + */ +#define OV_DL_ERR_FAIL() \ + if (true) { \ + ::OpenVic::dataloader::log::error("Method/function failed."); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_COND_MSG`. + * Only use this macro if more complex error detection or recovery is required. + * + * Prints `m_msg`, and the current function returns. + */ +#define OV_DL_ERR_FAIL_MSG(m_msg) \ + if (true) { \ + ::OpenVic::dataloader::log::error("{}\n\tMethod/function failed.", (m_msg)); \ + return; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_COND_V_MSG` or `OV_DL_ERR_FAIL_V_MSG`. + * Only use this macro if more complex error detection or recovery is required, and + * there is no sensible error message. + * + * The current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_V(m_retval) \ + if (true) { \ + ::OpenVic::dataloader::log::error("Method/function failed. Returning: {}", _OV_STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Try using `OV_DL_ERR_FAIL_COND_V_MSG`. + * Only use this macro if more complex error detection or recovery is required. + * + * Prints `m_msg`, and the current function returns `m_retval`. + */ +#define OV_DL_ERR_FAIL_V_MSG(m_retval, m_msg) \ + if (true) { \ + ::OpenVic::dataloader::log::error("{}\n\tMethod/function failed. Returning: {}", (m_msg), _OV_STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Prints `__VA_ARGS__` once during the application lifetime. + */ +#define OV_DL_ERR_PRINT_ONCE(...) \ + if (true) { \ + static bool warning_shown = false; \ + if (OV_unlikely(!warning_shown)) { \ + warning_shown = true; \ + ::OpenVic::dataloader::log::error(__VA_ARGS__); \ + } \ + } else \ + ((void)0) + +// Print warning message macros. + +/** + * Prints `__VA_ARGS__` once during the application lifetime. + * + * If warning about deprecated usage, use `OV_WARN_DEPRECATED` or `OV_WARN_DEPRECATED_MSG` instead. + */ +#define OV_DL_WARN_PRINT_ONCE(...) \ + if (true) { \ + static bool warning_shown = false; \ + if (OV_unlikely(!warning_shown)) { \ + warning_shown = true; \ + ::OpenVic::dataloader::log::warn(__VA_ARGS__); \ + } \ + } else \ + ((void)0) + +// Print deprecated warning message macros. + +/** + * Warns that the current function is deprecated. + */ +#define OV_DL_WARN_DEPRECATED OV_WARN_PRINT_ONCE("This method has been deprecated and will be removed in the future.") + +/** + * Warns that the current function is deprecated and prints `m_msg`. + */ +#define OV_DL_WARN_DEPRECATED_MSG(m_msg) \ + OV_DL_WARN_PRINT_ONCE("{}\n\tThis method has been deprecated and will be removed in the future.", (m_msg)) diff --git a/src/openvic-simulation/definition/dataloader/ListParser.hpp b/src/openvic-simulation/definition/dataloader/ListParser.hpp new file mode 100644 index 000000000..80e1b13f0 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/ListParser.hpp @@ -0,0 +1,239 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/error/Error.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/core/template/Concepts.hpp" +#include "openvic-simulation/definition/dataloader/ErrorMacros.hpp" +#include "openvic-simulation/definition/dataloader/MapInserter.hpp" +#include "openvic-simulation/definition/dataloader/Utility.hpp" +#include "openvic-simulation/definition/dataloader/ValueParser.hpp" + +namespace OpenVic::dataloader { + template + concept key_rule_concept = requires { + typename T::target_type; + { T::key } -> string_literal_concept; + }; + + template + concept key_rule_fits = key_rule_concept && std::same_as && (Key == Rule::key); + + template typename Rule, string_literal Key, typename T> + concept key_rule_template_fits = key_rule_fits, Key, T>; + + template + struct TraverseInitializeArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Node const* root_node; + T& out; + }; + + template + struct TraverseExtractArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Value const* node; + T& out; + bool& was_found; + Error& error; + }; + + struct TraverseFinalizeArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Node const* node; + memory::vector& expected; + bool was_found; + }; + + template + struct traverse_rule { + using target_type = T; + + static Error initialize(TraverseInitializeArguments args) { + if constexpr (requires { + { ValueExtractor::initialize({ args.parser, args.out }) } -> std::same_as; + }) { + return ValueExtractor::initialize({ args.parser, args.out }); + } + return Error::OK; + } + }; + + template + struct try_rule { + static Error finalize(TraverseFinalizeArguments args) { + if constexpr (requires { + { ValueExtractor::finalize({ args.parser }) } -> std::same_as; + }) { + return ValueExtractor::finalize({ args.parser }); + } + return Error::OK; + } + }; + + template + struct traverse_key_rule : traverse_rule { + static constexpr string_literal key = Key; + }; + + template + struct expect_key_rule : traverse_key_rule { + static Error finalize(TraverseFinalizeArguments args) { + if (!args.was_found) { + args.expected.emplace_back(Key); + return Error::FAILED; + } + return Error::OK; + } + }; + + template + struct try_key_rule : traverse_key_rule, try_rule {}; + + template + struct expect_once_key_rule : expect_key_rule { + static bool try_extract(TraverseExtractArguments args) { + args.error = Error::FAILED; + OV_DL_ERR_FAIL_COND_V_MSG( + args.was_found, true, make_location_message(args.parser, args.node, "Found multiple {} keys", Key) + ); + + args.error = ValueExtractor::extract({ args.parser, args.node, args.out }); + return true; + } + }; + + template< + string_literal Key, + typename T, + auto Function, + typename ItemType = std::conditional_t, + typename ParamType = std::conditional_t, T, type_safe::output_parameter>> + requires strict_regular_invocable_r, Error, ValueExtractorArguments> + struct expect_many_key_rule : expect_key_rule { + using item_type = ItemType; + using parameter_type = ParamType; + + static bool try_extract(TraverseExtractArguments args) { + if constexpr (std::same_as) { + args.error = Function({ args.parser, args.node, args.out }); + } else { + type_safe::deferred_construction item; + args.error = Function({ args.parser, args.node, type_safe::out(item) }); + if (args.error == Error::OK && item.has_value()) { + MapCallback::insert({ args.parser, args.node, args.out }, item.value()); + } + } + return true; + } + }; + + template + struct try_once_key_rule : try_key_rule { + static bool try_extract(TraverseExtractArguments args) { + args.error = Error::FAILED; + OV_DL_ERR_FAIL_COND_V_MSG( + args.was_found, true, make_location_message(args.parser, args.node, "Found multiple {} keys", Key) + ); + + args.error = ValueExtractor::extract({ args.parser, args.node, args.out }); + return true; + } + }; + + template< + string_literal Key, + typename T, + auto Function, + typename ItemType = std::conditional_t, + typename ParamType = std::conditional_t, T, type_safe::output_parameter>> + requires strict_regular_invocable_r, Error, ValueExtractorArguments> + struct try_many_key_rule : try_key_rule { + using item_type = ItemType; + using parameter_type = ParamType; + + static bool try_extract(TraverseExtractArguments args) { + if constexpr (std::same_as) { + args.error = Function({ args.parser, args.node, args.out }); + } else { + type_safe::deferred_construction item; + args.error = Function({ args.parser, args.node, type_safe::out(item) }); + if (args.error == Error::OK && item.has_value()) { + MapCallback::insert({ args.parser, args.node, args.out }, item.value()); + } + } + return true; + } + }; + + template + struct set_when_key_rule : try_key_rule { + static bool try_extract(TraverseExtractArguments args) { + args.error = ValueExtractor::try_extract({ args.parser, args.node, args.out }); + return true; + } + }; + + struct DefaultOptions { + spdlog::level::level_enum duplicates_level = spdlog::level::warn; + }; + + template + struct DefaultFunctionArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::AssignStatement const* node; + MapT& map; + }; + + template + struct try_default_map_rule : try_rule> { + using target_type = MapT; + using value_type = ValueT; + + static Error call(DefaultFunctionArguments args) { + auto* left = dryad::node_try_cast(args.node->left()); + if (!left) { + return Error::SKIP; + } + + if constexpr (Options.duplicates_level != spdlog::level::off) { + if (MapCallback::has({ args.parser, left, args.map })) { + dataloader::log::log( + Options.duplicates_level, + dataloader::make_location_message(args.parser, left, "Found multiple {} keys", left->value().view()) + ); + if constexpr (Options.duplicates_level >= spdlog::level::err) { + return Error::FAILED; + } + } + } + + type_safe::deferred_construction value; + Error err = ValueExtractor>::extract( + { args.parser, left, type_safe::out(value) } + ); + + if (err == Error::OK && value.has_value()) { + return MapCallback::insert({ args.parser, left, args.map }, value.value()); + } + return err; + } + }; + + struct ApplyFunctionArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::AssignStatement const* node; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/Logger.cpp b/src/openvic-simulation/definition/dataloader/Logger.cpp new file mode 100644 index 000000000..39994b041 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/Logger.cpp @@ -0,0 +1,50 @@ +#include "Logger.hpp" + +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +using namespace OpenVic::dataloader; + +std::shared_ptr logger_registry::default_logger() { + std::lock_guard lock(logger_map_mutex); + return logger; +} + +spdlog::logger* logger_registry::get_default_raw() { + return logger.get(); +} + +logger_registry& logger_registry::instance() { + static logger_registry registry; + return registry; +} + +logger_registry::logger_registry() { +#ifdef _WIN32 + auto color_sink = std::make_shared(); +#else + auto color_sink = std::make_shared(); +#endif + + auto logger = std::make_shared(std::string { logger_name }, std::move(color_sink)); + spdlog::register_logger(logger); +} + +logger_registry::~logger_registry() = default; + +std::shared_ptr OpenVic::dataloader::default_logger() { + return logger_registry::instance().default_logger(); +} + +spdlog::logger* OpenVic::dataloader::default_logger_raw() { + return logger_registry::instance().get_default_raw(); +} diff --git a/src/openvic-simulation/definition/dataloader/Logger.hpp b/src/openvic-simulation/definition/dataloader/Logger.hpp new file mode 100644 index 000000000..79a5a97a3 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/Logger.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/template/Concepts.hpp" + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace OpenVic::dataloader { + static constexpr std::string_view logger_name = "dataloader logger"; + + class logger_registry { + public: + logger_registry(logger_registry const&) = delete; + logger_registry& operator=(logger_registry const&) = delete; + + std::shared_ptr default_logger(); + + // Return raw ptr to the default logger. + // To be used directly by the dataloader logger default api + // This make the default API faster, but cannot be used concurrently with set_default_logger(). + // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. + spdlog::logger* get_default_raw(); + + static logger_registry& instance(); + + private: + logger_registry(); + ~logger_registry(); + + std::mutex logger_map_mutex; + std::shared_ptr logger; + }; + + std::shared_ptr default_logger(); + spdlog::logger* default_logger_raw(); + + namespace log { + template + struct fstring : spdlog::format_string_t { + using t = fstring; + using base_type = spdlog::format_string_t; + + std::source_location loc; + + template + consteval FMT_ALWAYS_INLINE fstring( + const char (&s)[N], std::source_location const& loc = std::source_location::current() + ) : base_type { s }, loc { loc } {} + + template::value)> + consteval FMT_ALWAYS_INLINE fstring(S const& s, std::source_location const& loc = std::source_location::current()) : + base_type { s }, loc { loc } {} + + template< + typename S, + FMT_ENABLE_IF( + (std::is_base_of::value) && std::is_same::value + )> + fstring(S const&, std::source_location const& loc = std::source_location::current()) : + base_type { S() }, loc { loc } {} + + fstring(fmt::runtime_format_string<> fmt, std::source_location const& loc = std::source_location::current()) : + base_type(fmt), loc { loc } {} + }; + + template + using format_string = typename fstring::t; + + namespace detail { + template + concept not_convertible_to_any_format_string = !spdlog::is_convertible_to_any_format_string::value; + + template + concept runtime_log_string = + !specialization_of && spdlog::is_convertible_to_any_format_string::value && + !constexpr_constructible; + + template + concept runtime_string = + spdlog::is_convertible_to_any_format_string::value && !constexpr_constructible; + } + + inline void vlog( + std::source_location const& loc, spdlog::level::level_enum lvl, spdlog::string_view_t fmt, fmt::format_args args + ) { + spdlog::memory_buf_t buf; + fmt::vformat_to(fmt::appender(buf), fmt, args); + + default_logger_raw()->log( + spdlog::source_loc { loc.file_name(), static_cast(loc.line()), loc.function_name() }, + lvl, + spdlog::string_view_t { buf.data(), buf.size() } + ); + } + + template + inline void log(spdlog::level::level_enum lvl, format_string fmt, Args&&... args) { + vlog(fmt.loc, lvl, fmt, fmt::make_format_args(args...)); + } + + template + inline void trace(format_string fmt, Args&&... args) { + log(spdlog::level::trace, fmt, std::forward(args)...); + } + + template + inline void debug(format_string fmt, Args&&... args) { + log(spdlog::level::debug, fmt, std::forward(args)...); + } + + template + inline void info(format_string fmt, Args&&... args) { + log(spdlog::level::info, fmt, std::forward(args)...); + } + + template + inline void warn(format_string fmt, Args&&... args) { + log(spdlog::level::warn, fmt, std::forward(args)...); + } + + template + inline void error(format_string fmt, Args&&... args) { + log(spdlog::level::err, fmt, std::forward(args)...); + } + + template + inline void critical(format_string fmt, Args&&... args) { + log(spdlog::level::critical, fmt, std::forward(args)...); + } + + template + inline void log( + spdlog::level::level_enum lvl, T const& v, std::source_location const& loc = std::source_location::current() + ) { + vlog(loc, lvl, "{}", fmt::make_format_args(v)); + } + + template + inline void trace(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::trace, v, loc); + } + + template + inline void debug(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::debug, v, loc); + } + + template + inline void info(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::info, v, loc); + } + + template + inline void warn(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::warn, v, loc); + } + + template + inline void error(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::err, v, loc); + } + + template + inline void critical(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::critical, v, loc); + } + + template + inline void log( + spdlog::level::level_enum lvl, T const& v, std::source_location const& loc = std::source_location::current() + ) { + vlog(loc, lvl, "{}", fmt::make_format_args(v)); + } + + template + inline void trace(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::trace, v, loc); + } + + template + inline void debug(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::debug, v, loc); + } + + template + inline void info(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::info, v, loc); + } + + template + inline void warn(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::warn, v, loc); + } + + template + inline void error(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::err, v, loc); + } + + template + inline void critical(T const& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::critical, v, loc); + } + + template T> + inline void log( + spdlog::level::level_enum lvl, T&& v, std::source_location const& loc = std::source_location::current() + ) { + vlog(loc, lvl, "{}", fmt::make_format_args(v)); + } + + template T> + inline void trace(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::trace, v, loc); + } + + template T> + inline void debug(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::debug, v, loc); + } + + template T> + inline void info(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::info, v, loc); + } + + template T> + inline void warn(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::warn, v, loc); + } + + template T> + inline void error(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::err, v, loc); + } + + template T> + inline void critical(T&& v, std::source_location const& loc = std::source_location::current()) { + log(spdlog::level::critical, v, loc); + } + } +} diff --git a/src/openvic-simulation/definition/dataloader/MapInserter.hpp b/src/openvic-simulation/definition/dataloader/MapInserter.hpp new file mode 100644 index 000000000..c75e9f5be --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/MapInserter.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace OpenVic::dataloader { + template + struct MapCallbackArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Value const* node; + MapT& map; + }; + + template + struct BaseMapCallback { + static bool has(MapCallbackArguments args) { + return false; + } + }; + + template + struct MapCallback; +} diff --git a/src/openvic-simulation/definition/dataloader/TreeTraverse.hpp b/src/openvic-simulation/definition/dataloader/TreeTraverse.hpp new file mode 100644 index 000000000..81b459c21 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/TreeTraverse.hpp @@ -0,0 +1,460 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include + +#include "openvic-simulation/core/error/Error.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/core/template/Concepts.hpp" +#include "openvic-simulation/definition/dataloader/ErrorMacros.hpp" +#include "openvic-simulation/definition/dataloader/ListParser.hpp" +#include "openvic-simulation/definition/dataloader/MapInserter.hpp" +#include "openvic-simulation/definition/dataloader/Utility.hpp" +#include "openvic-simulation/definition/dataloader/ValueParser.hpp" + +namespace OpenVic { + struct TreeOptions { + spdlog::level::level_enum unknown_level = spdlog::level::off; + }; + + template + class TreeTraverse { + public: + using parser_type = ovdl::v2script::Parser; + using parser_pointer_type = parser_type const*; + + using node_type = ovdl::v2script::ast::Node; + using node_pointer_type = node_type const*; + + using value_node_type = ovdl::v2script::ast::Value; + using value_node_pointer_type = value_node_type const*; + + using statement_range_iterator = node_type::_children_range::iterator; + using statement_range_type = dryad::node_range; + + static constexpr bool is_apply_empty = std::same_as; + using apply_type = std::conditional_t; + + static constexpr bool is_default_element_empty = std::same_as; + using default_element_target_type = dataloader::conditional_target_type; + using default_element_target_pointer_type = dataloader::conditional_target_pointer_type; + + constexpr TreeTraverse() + requires(is_apply_empty && is_default_element_empty) + = default; + + constexpr TreeTraverse(apply_type a, default_element_target_pointer_type map, typename Elements::target_type*... refs) : + apply_function(std::move(a)), map { map }, targets { refs... } {} + + template + constexpr auto options() const { + return _make_new_traverse_options(std::make_index_sequence {}); + } + + template + requires requires { sizeof(dataloader::ValueExtractor); } + constexpr auto expect_once(T& ref) const { + return append_key_rule(ref); + } + + template + requires requires(parser_pointer_type p, value_node_pointer_type vn, T& o) { + { Function({ p, vn, o }) } -> std::convertible_to; + } || requires(parser_pointer_type p, value_node_pointer_type vn) { + typename T::value_type; + sizeof(dataloader::MapCallback); + { Function({ p, vn, std::declval>() }) } -> std::convertible_to; + } + constexpr auto expect_many(T& ref) const { + return append_key_rule>(ref); + } + + template + requires requires { sizeof(dataloader::ValueExtractor); } + constexpr auto expect_many(T& ref) const { + return expect_many::extract>(ref); + } + + template + requires requires { sizeof(dataloader::ValueExtractor); } + constexpr auto try_once(T& ref) const { + return append_key_rule(ref); + } + + template + requires requires(parser_pointer_type p, value_node_pointer_type vn, T& o) { + { Function({ p, vn, o }) } -> std::convertible_to; + } || requires(parser_pointer_type p, value_node_pointer_type vn) { + typename T::value_type; + sizeof(dataloader::MapCallback); + { Function({ p, vn, std::declval>() }) } -> std::convertible_to; + } + constexpr auto try_many(T& ref) const { + return append_key_rule>(ref); + } + + template + requires requires { sizeof(dataloader::ValueExtractor); } + constexpr auto try_many(T& ref) const { + return try_many::extract>(ref); + } + + template + requires requires { sizeof(dataloader::ValueExtractor); } + constexpr auto set_when(T& ref) const { + return append_key_rule(ref); + } + + template + requires requires { typename MapT::value_type; } + constexpr auto try_default(MapT& map) const { + return try_default(map); + } + + template + requires requires { + sizeof(dataloader::ValueExtractor>); + sizeof(dataloader::MapCallback); + } + constexpr auto try_default(MapT& map) const { + return set_default>(map); + } + + template typename DefaultRuleTemplate, typename MapT> + requires requires { typename MapT::value_type; } + constexpr auto set_default_rule(MapT& map) const { + return set_default_rule>(map); + } + + template typename DefaultRuleTemplate, typename MapT> + requires requires { + typename DefaultRuleTemplate::value_type; + } && std::same_as::value_type> + constexpr auto set_default_rule(MapT& map) const { + return set_default_rule>(map); + } + + template + requires requires(dataloader::DefaultFunctionArguments args) { + typename DefaultRule::target_type; + typename DefaultRule::value_type; + { DefaultRule::call(args) } -> std::same_as; + } && std::same_as + constexpr auto set_default_rule(MapT& map) const { + return _make_new_traverse_default( + map, std::make_index_sequence {} + ); + } + + template Func> + constexpr auto apply(Func&& func) const { + using NewApply = std::decay_t; + return _make_new_traverse_apply( + std::forward(func), std::make_index_sequence {} + ); + } + + template T> + constexpr auto append_key_rule(T& ref) const { + return _make_new_traverse_append(std::make_index_sequence {}, ref); + } + + template typename NewElement, typename T> + requires dataloader::key_rule_template_fits + constexpr auto append_key_rule(T& ref) const { + return _make_new_traverse_append>(std::make_index_sequence {}, ref); + } + + Error operator()(ovdl::v2script::Parser const& parser, node_pointer_type node) const { + return operator()(&parser, node); + } + + Error operator()(ovdl::v2script::Parser const& parser) const { + return operator()(&parser, parser.get_file_node(), parser.get_file_node()->statements()); + } + + Error operator()(node_pointer_type node) const { + return operator()(nullptr, node); + } + + Error operator()(parser_pointer_type parser, node_pointer_type node) const { + using namespace ovdl::v2script::ast; + + Error err = Error::OK; + + auto statements = [&]() -> statement_range_type { + if (auto* ft = dryad::node_try_cast(node)) { + return ft->statements(); + } + + if (auto* lv = dryad::node_try_cast(node)) { + return lv->statements(); + } + + err = Error::FAILED; + OV_DL_ERR_FAIL_V_MSG( + dryad::make_node_range( + statement_range_iterator::from_ptr(nullptr), statement_range_iterator::from_ptr(nullptr) + ), + dataloader::make_location_message( + parser, node, "Expected a file tree or list value, found {}", get_kind_name(node->kind()) + ) + ); + }(); + + if (err != Error::OK) { + return err; + } + + return operator()(parser, node, statements); + } + + Error operator()(parser_pointer_type parser, node_pointer_type node, statement_range_type statements) const { + using namespace ovdl::v2script::ast; + + Error err = Error::OK; + std::array found {}; + + auto key_list = ([&]()->std::array, sizeof...(Elements)> { + if (parser == nullptr) { + return {}; + } + + return { parser->find_intern(Elements::key)... }; + }()); + + err = initialize_all(parser, node, err, std::make_index_sequence {}); + + for (Statement const* s : statements) { + auto* as = dryad::node_try_cast(s); + if (!as) { + continue; + } + + auto* left = dryad::node_try_cast(as->left()); + if (!left) { + continue; + } + + bool handled = false; + + // Linear search – sizeof...(Elements) is tiny, so this is fine + // and still completely optimisable by the compiler. + const ovdl::symbol<> symbol = left->value(); + handled = try_all( + { parser, symbol, as->right(), err, found }, key_list, std::make_index_sequence {} + ); + + if (handled) { + if (err != Error::OK || err != Error::SKIP) { + return err; + } + handled = err == Error::OK; + } else if constexpr (!is_default_element_empty) { + Error err = Error::OK; + if (err = DefaultElement::call({ parser, as, map }); err != Error::OK || err != Error::SKIP) { + return err; + } + handled = err == Error::OK; + } + + if (handled) { + continue; + } + + if constexpr (!is_apply_empty) { + if (Error err = apply_function(dataloader::ApplyFunctionArguments { parser, as }); + err != Error::OK || err != Error::SKIP) { + return err; + } + handled = err == Error::OK; + } + + if constexpr (Options.unknown_level != spdlog::level::off) { + if (!handled) { + if constexpr (Options.unknown_level >= spdlog::level::err) { + err = Error::FAILED; + } + dataloader::log::log( + Options.unknown_level, dataloader::make_location_message(parser, s, "Unknown key {}", symbol.view()) + ); + } + } + } + + err = finalize_all(parser, node, found, std::make_index_sequence {}); + return err; + } + + private: + std::tuple targets; + OV_NO_UNIQUE_ADDRESS apply_type apply_function; + OV_NO_UNIQUE_ADDRESS default_element_target_pointer_type map; + + struct TryArguments { + parser_pointer_type parser = nullptr; + ovdl::symbol<> key; + value_node_pointer_type value = nullptr; + Error& error; + std::array& found_list; + }; + + template + constexpr auto _make_new_traverse_append(std::index_sequence, T& ref) const { + using NewTraverse = TreeTraverse; + return NewTraverse { + apply_function, + map, + std::get(targets)..., + &ref, + }; + } + + template + constexpr auto _make_new_traverse_options(std::index_sequence) const { + using NewTraverse = TreeTraverse; + return NewTraverse { apply_function, map, *std::get(targets)... }; + } + + template + requires(is_apply_empty && !std::same_as) + constexpr auto _make_new_traverse_apply(NewApply&& func, std::index_sequence) const { + using NewTraverse = TreeTraverse; + return NewTraverse { + std::move(func), + map, + *std::get(targets)..., + }; + } + + template + requires(is_default_element_empty && !std::same_as) + constexpr auto _make_new_traverse_default(MapT& ref, std::index_sequence) const { + using NewTraverse = TreeTraverse; + return NewTraverse { + apply_function, + &ref, + *std::get(targets)..., + }; + } + + template + Error initialize_all( + parser_pointer_type parser, node_pointer_type root_node, Error& error, std::index_sequence + ) const { + bool failed = false; + ((failed |= + Elements::initialize(dataloader::TraverseInitializeArguments { parser, root_node, *std::get(targets) }) != + Error::OK), + ...); + + return failed ? Error::FAILED : Error::OK; + } + + template + bool try_all( + TryArguments args, std::array, sizeof...(Elements)> const& key_list, std::index_sequence + ) const { + bool handled = false; + + if (args.parser == nullptr) { + // Iterates Elements until Elements::key == key + ((handled = + handled || + (Elements::key == args.key.view() && + Elements::try_extract( + dataloader::TraverseExtractArguments { + args.parser, + args.value, + *std::get(targets), + args.found_list[Is], + args.error, + } + ))), + ...); + + return handled; + } + + for (size_t i = 0; i < key_list.size(); i++) { + const ovdl::symbol<> symbol = key_list[i]; + if (!symbol || symbol != args.key) { + continue; + } + + // Iterates Elements until Is == i + ((handled = + handled || + (Is == i && + Elements::try_extract( + dataloader::TraverseExtractArguments { + args.parser, + args.value, + *std::get(targets), + args.found_list[Is], + args.error, + } + ))), + ...); + + if (handled) { + return true; + } + } + + return false; + } + + template + Error finalize_all( + parser_pointer_type parser, + node_pointer_type node, + std::array const& found, + std::index_sequence + ) const { + // TODO: make inplace_vector + memory::vector expected_values; + expected_values.reserve(sizeof...(Elements)); + + bool failed = false; + ((failed |= + Elements::finalize(dataloader::TraverseFinalizeArguments { parser, node, expected_values, found[Is] }) != + Error::OK), + ...); + + if constexpr (!is_default_element_empty) { + failed |= + DefaultElement::finalize(dataloader::TraverseFinalizeArguments { parser, node, expected_values, true }) != + Error::OK; + } + + if (!expected_values.empty()) { + OV_DL_ERR_FAIL_COND_V_MSG( + failed, + Error::FAILED, + dataloader::make_location_message(parser, node, "Expected key: {}", fmt::join(expected_values, ", ")) + ); + } + + return failed ? Error::FAILED : Error::OK; + } + }; + + inline constexpr TreeTraverse<{}, void, void> Traverse {}; +} diff --git a/src/openvic-simulation/definition/dataloader/Utility.hpp b/src/openvic-simulation/definition/dataloader/Utility.hpp new file mode 100644 index 000000000..dabc78c39 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/Utility.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +#include "openvic-simulation/core/memory/Formatting.hpp" + +namespace OpenVic::dataloader { + template + memory::string make_location_message( + ovdl::v2script::Parser const* parser, ovdl::v2script::ast::Node const* node, fmt::format_string fmt, T&&... args + ) { + memory::fmt::basic_memory_buffer result {}; + auto out = std::back_inserter(result); + if (parser) { + ovdl::FilePosition pos = parser->get_position(node); + out = fmt::format_to(out, "{}:{}:{}: ", parser->get_file_path(), pos.start_line, pos.start_column); + } + out = fmt::format_to(out, fmt, std::forward(args)...); + return { result.data(), result.size() }; + } + + struct empty_type {}; + + template + struct conditional_target_type { + using type = T::target_type; + }; + + template<> + struct conditional_target_type { + using type = empty_type; + }; + + template + struct conditional_target_pointer_type { + using type = T::target_type; + }; + + template<> + struct conditional_target_pointer_type { + using type = empty_type; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/ValueParser.cpp b/src/openvic-simulation/definition/dataloader/ValueParser.cpp new file mode 100644 index 000000000..00fb551c0 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/ValueParser.cpp @@ -0,0 +1,406 @@ +#include "ValueParser.hpp" + +#include +#include +#include + +#include +#include + +#include + +#include + +#include "openvic-simulation/core/error/Error.hpp" +#include "openvic-simulation/core/error/ErrorMacros.hpp" +#include "openvic-simulation/core/object/Colour.hpp" +#include "openvic-simulation/core/object/Date.hpp" +#include "openvic-simulation/core/object/FixedPoint.hpp" +#include "openvic-simulation/core/object/FixedPoint/String.hpp" +#include "openvic-simulation/core/object/Timespan.hpp" +#include "openvic-simulation/core/object/Vector.hpp" +#include "openvic-simulation/definition/dataloader/ErrorMacros.hpp" +#include "openvic-simulation/definition/dataloader/Logger.hpp" +#include "openvic-simulation/definition/dataloader/TreeTraverse.hpp" +#include "openvic-simulation/definition/dataloader/Utility.hpp" + +using namespace OpenVic; +using namespace OpenVic::dataloader; + +Error ValueExtractor>>::extract(ValueExtractorArguments>> args) { + if (auto* fv = dryad::node_try_cast(args.node)) { + args.out.value = fv->value(); + return Error::OK; + } + + OV_DL_ERR_FAIL_V_MSG( + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected a string, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind()) + ) + ); +} + +Error ValueExtractor>::extract(ValueExtractorArguments> args) { + emptyable> e { args.out }; + OV_RETURN_IF_ERROR(ValueExtractor>>::extract({ args.parser, args.node, e })); + + ovdl::symbol<> symbol = args.out; + args.out = {}; + OV_DL_ERR_FAIL_COND_V_MSG( + symbol.view().empty(), Error::FAILED, make_location_message(args.parser, args.node, "Unexpected empty string") + ); + args.out = symbol; + + return Error::OK; +} + +Error ValueExtractor>::extract(ValueExtractorArguments> args) { + ovdl::symbol<> symbol; + emptyable> e { symbol }; + OV_RETURN_IF_ERROR(ValueExtractor>>::extract({ args.parser, args.node, e })); + args.out.value = symbol.view(); + return Error::OK; +} + + +Error ValueExtractor::extract(ValueExtractorArguments args) { + ovdl::symbol<> symbol; + OV_RETURN_IF_ERROR(ValueExtractor>::extract({ args.parser, args.node, symbol })); + args.out = symbol.view(); + return Error::OK; +} + +Error ValueExtractor::extract(ValueExtractorArguments args) { + uint64_t out; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, out })); + + if (out > 1) { + log::warn(make_location_message(args.parser, args.node, "Found integer bool with value {} instead of 0 or 1", out)); + } + + args.out.value = out != 0; + return Error::OK; +} + +Error ValueExtractor::extract(ValueExtractorArguments args) { + std::string_view sv; + + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, sv })); + + fixed_point_t f; + std::from_chars_result result = fp::from_chars_with_plus(f, sv.data(), sv.data() + sv.size()); + if (result.ec == std::errc {}) { + args.out = f; + return Error::OK; + } + + OV_DL_ERR_FAIL_V_MSG( + Error::FAILED, make_location_message(args.parser, args.node, "Expected a fixed point value, found {}", sv) + ); +} + +template +Error ValueExtractor>::extract(ValueExtractorArguments> args) { + if (auto* lv = dryad::node_try_cast(args.node)) { + return Traverse.options<{ .unknown_level = spdlog::level::err }>() + .template expect_once<"x">(args.out.x) + .template expect_once<"y">(args.out.y)(*args.parser, lv); + } + + OV_DL_ERR_FAIL_V_MSG( + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected a list value, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind()) + ) + ); +} + +template +Error ValueExtractor>::extract(ValueExtractorArguments> args) { + auto const* lv = dryad::node_try_cast(args.node); + OV_DL_ERR_FAIL_NULL_V_MSG( + lv, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected a list value, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind()) + ) + ); + + size_t count; + auto statements = lv->statements(); + for (auto [index, sub_node] : statements | ranges::views::enumerate) { + count = index + 1; + if (index >= 3) { + continue; + } + + auto const* value = dryad::node_try_cast(sub_node); + OV_DL_ERR_CONTINUE_MSG( + value == nullptr, + make_location_message( + args.parser, + sub_node, + "Expected a value statement, found {}", + ovdl::v2script::ast::get_kind_name(sub_node->kind()) + ) + ); + + T tmp; + if (ValueExtractor::extract({ args.parser, value->value(), tmp }) != Error::OK) { + continue; + } + args.out[index] = tmp; + } + + OV_DL_ERR_FAIL_COND_V_MSG( + count >= 3, Error::FAILED, make_location_message(args.parser, args.node, "Expected 3 values in list, found {}", count) + ); + + return Error::OK; +} + +template +Error ValueExtractor>::extract(ValueExtractorArguments> args) { + auto const* lv = dryad::node_try_cast(args.node); + OV_DL_ERR_FAIL_NULL_V_MSG( + lv, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected a list value, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind()) + ) + ); + + size_t count; + auto statements = lv->statements(); + for (auto [index, sub_node] : statements | ranges::views::enumerate) { + count = index + 1; + if (index >= 4) { + continue; + } + + auto const* value = dryad::node_try_cast(sub_node); + OV_DL_ERR_CONTINUE_MSG( + value == nullptr, + make_location_message( + args.parser, + sub_node, + "Expected a value statement, found {}", + ovdl::v2script::ast::get_kind_name(sub_node->kind()) + ) + ); + + T tmp; + if (ValueExtractor::extract({ args.parser, value->value(), tmp }) != Error::OK) { + continue; + } + args.out[index] = tmp; + } + + OV_DL_ERR_FAIL_COND_V_MSG( + count >= 4, Error::FAILED, make_location_message(args.parser, args.node, "Expected 4 values in list, found {}", count) + ); + + return Error::OK; +} + +Error ValueExtractor::extract(ValueExtractorArguments args) { + Timespan::value_t value; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, value })); + args.out.value = Timespan::from_years(value); + return Error::OK; +} + +Error ValueExtractor::extract(ValueExtractorArguments args) { + Timespan::value_t value; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, value })); + args.out.value = Timespan::from_months(value); + return Error::OK; +} + +Error ValueExtractor::extract(ValueExtractorArguments args) { + Timespan::value_t value; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, value })); + args.out.value = Timespan::from_days(value); + return Error::OK; +} + +// TODO: Allow providing spdlog::logger* to Date::from_string_log +Error ValueExtractor::extract(ValueExtractorArguments args) { + std::string_view sv; + + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, sv })); + + Date date; + Date::from_chars_result result = date.from_chars(sv.data(), sv.data() + sv.size()); + + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::invalid_argument && result.type == Date::errc_type::year && result.ptr == result.type_first, + Error::FAILED, + make_location_message(args.parser, args.node, "Could not parse year value") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::value_too_large && result.type == Date::errc_type::year, + Error::FAILED, + make_location_message(args.parser, args.node, "Year value was too large or too small") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::result_out_of_range && result.type == Date::errc_type::year, + Error::FAILED, + make_location_message(args.parser, args.node, "Only year value could be found") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::invalid_argument && result.type == Date::errc_type::year && result.ptr != result.type_first, + Error::FAILED, + make_location_message(args.parser, args.node, "Year value was missing a separator (\"{}\")", Date::SEPARATOR_CHARACTER) + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::invalid_argument && result.type == Date::errc_type::month && result.ptr == result.type_first, + Error::FAILED, + "Could not parse month value." + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::not_supported && result.type == Date::errc_type::month, + Error::FAILED, + make_location_message(args.parser, args.node, "Month value cannot be 0") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::value_too_large && result.type == Date::errc_type::month && result.ptr == result.type_first, + Error::FAILED, + make_location_message(args.parser, args.node, "Month value cannot be larger than {}", Date::MONTHS_IN_YEAR) + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::result_out_of_range && result.type == Date::errc_type::month, + Error::FAILED, + "Only year and month value could be found." + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::invalid_argument && result.type == Date::errc_type::month && result.ptr != result.type_first, + Error::FAILED, + make_location_message(args.parser, args.node, "Month value was missing a separator (\"{}\")", Date::SEPARATOR_CHARACTER) + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::invalid_argument && result.type == Date::errc_type::day && result.ptr == result.type_first, + Error::FAILED, + make_location_message(args.parser, args.node, "Could not parse day value") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::not_supported && result.type == Date::errc_type::day, + Error::FAILED, + make_location_message(args.parser, args.node, "Day value cannot be 0") + ); + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::value_too_large && result.type == Date::errc_type::day && result.ptr == result.type_first, + Error::FAILED, + make_location_message( + args.parser, + args.node, + "Day value cannot be larger than {} for {}", + Date::DAYS_IN_MONTH[date.get_month() - 1], + date.get_month() + ) + ); + + return Error::OK; +} + +template +Error ValueExtractor>::extract( + ValueExtractorArguments> args +) { + using colour_t = basic_colour_t; + + auto const* lv = dryad::node_try_cast(args.node); + OV_DL_ERR_FAIL_NULL_V_MSG( + lv, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected a list value, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind()) + ) + ); + + size_t count; + auto statements = lv->statements(); + for (auto [index, sub_node] : statements | ranges::views::enumerate) { + count = index + 1; + if (index >= 3) { + continue; + } + + auto const* value = dryad::node_try_cast(sub_node); + OV_DL_ERR_CONTINUE_MSG( + value == nullptr, + make_location_message( + args.parser, + sub_node, + "Expected a value statement, found {}", + ovdl::v2script::ast::get_kind_name(sub_node->kind()) + ) + ); + + fixed_point_t tmp; + if (ValueExtractor::extract({ args.parser, value->value(), tmp }) != Error::OK) { + continue; + } + + OV_DL_ERR_CONTINUE_MSG( + tmp < 0 || tmp > 255, + make_location_message( + args.parser, + value->value(), + "Expected color component fractional between 0 and 1 or integer between 0 and 255, found {}", + tmp + ) + ); + + auto trunc = tmp.truncate(); + if (tmp <= 1) { + tmp *= 255; + } else if (!tmp.is_negative()) { + log::warn(make_location_message( + args.parser, + value->value(), + "Expected color component fractional between 0 and 1 or integer between 0 and 255, found fractional {}, " + "truncating to {}", + tmp, + trunc + )); + } + args.out[index] = trunc; + } + + OV_DL_ERR_FAIL_COND_V_MSG( + count >= 3, Error::FAILED, make_location_message(args.parser, args.node, "Expected 3 values in list, found {}", count) + ); + + return Error::OK; +} + +template +Error ValueExtractor>>::extract( + ValueExtractorArguments>> args +) { + using colour_t = basic_colour_t; + + typename colour_t::integer_type integer; + hex out { integer }; + OV_RETURN_IF_ERROR((ValueExtractor>::extract({ args.parser, args.node, out }))); + + args.out.value = colour_t::from_argb(integer); + return Error::OK; +} + +template struct OpenVic::dataloader::ValueExtractor; +template struct OpenVic::dataloader::ValueExtractor; + +template struct OpenVic::dataloader::ValueExtractor; +template struct OpenVic::dataloader::ValueExtractor; + +template struct OpenVic::dataloader::ValueExtractor; +template struct OpenVic::dataloader::ValueExtractor; + +template struct OpenVic::dataloader::ValueExtractor; +template struct OpenVic::dataloader::ValueExtractor; + +template struct OpenVic::dataloader::ValueExtractor>; +template struct OpenVic::dataloader::ValueExtractor>; diff --git a/src/openvic-simulation/definition/dataloader/ValueParser.hpp b/src/openvic-simulation/definition/dataloader/ValueParser.hpp new file mode 100644 index 000000000..76c855f27 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/ValueParser.hpp @@ -0,0 +1,488 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/error/Error.hpp" +#include "openvic-simulation/core/error/ErrorMacros.hpp" +#include "openvic-simulation/core/string/CharConv.hpp" +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/core/ui/TextFormat.hpp" +#include "openvic-simulation/definition/dataloader/ErrorMacros.hpp" +#include "openvic-simulation/definition/dataloader/Utility.hpp" + +namespace OpenVic { + class fixed_point_t; + + template + struct vec2_t; + template + struct vec3_t; + template + struct vec4_t; + + class Timespan; + class Date; + + template + struct colour_traits; + + template + class basic_colour_t; + + enum class text_format_t : uint8_t; +} + +namespace OpenVic::dataloader { + template + struct strict_id { + T& value; + }; + + template + struct strict_string { + T& value; + }; + + template + struct emptyable { + T& value; + }; + + struct int_bool { + bool& value; + }; + + template + struct base { + static constexpr std::integral_constant base_value = {}; + T& value; + }; + + template + using binary = base; + + template + using octal = base; + + template + using decimal = base; + + template + using hex = base; + + struct years { + Timespan& value; + }; + + struct months { + Timespan& value; + }; + + struct days { + Timespan& value; + }; + + template + struct overwrite_optional : std::optional { + using base_type = std::optional; + using base_type::base_type; + }; + + template + struct KeyValue { + static constexpr auto key = Key; + static constexpr auto value = Value; + }; + + template + inline static constexpr auto KV = KeyValue {}; + + template + struct ValueMapper; + + template + struct ValueInitializeArguments { + ovdl::v2script::Parser const* parser; + T& out; + }; + + template + struct ValueExtractorArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Value const* node; + T& out; + }; + + template + struct ValueExtractorArguments> { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Value const* node; + type_safe::output_parameter out; + }; + + template + struct ValueExtractorArguments { + ovdl::v2script::Parser const* parser; + ovdl::v2script::ast::Value const* node; + T& out; + }; + + template + struct ValueFinalizeArguments { + ovdl::v2script::Parser const* parser; + T& out; + }; + + template + struct ValueExtractor; + + template<> + struct ValueExtractor>> { + static Error extract(ValueExtractorArguments>> args); + }; + + template<> + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template<> + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template + struct ValueExtractor { + static Error extract(ValueExtractorArguments args) { + std::string_view sv; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, sv })); + + int64_t tmp; + std::from_chars_result result = from_chars(sv.data(), sv.data() + sv.size(), tmp); + if (result.ec == std::errc {}) { + args.out = tmp; + return Error::OK; + } + + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::result_out_of_range, + Error::FAILED, + make_location_message(args.parser, args.node, "Overflow of integer, found {}", sv) + ); + + OV_DL_ERR_FAIL_V_MSG( + Error::FAILED, make_location_message(args.parser, args.node, "Expected an integer, found {}", sv) + ); + } + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + return ValueExtractor::extract({ args.parser, args.node, args.out.value }); + } + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + std::string_view sv; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, sv })); + + T tmp; + std::from_chars_result result = from_chars(sv.data(), sv.data() + sv.size(), tmp, Base); + if (result.ec == std::errc {}) { + args.out.value = tmp; + return Error::OK; + } + + OV_DL_ERR_FAIL_COND_V_MSG( + result.ec == std::errc::result_out_of_range, + Error::FAILED, + make_location_message(args.parser, args.node, "Overflow of integer, found {}", sv) + ); + + OV_DL_ERR_FAIL_V_MSG( + Error::FAILED, make_location_message(args.parser, args.node, "Expected an integer, found {}", sv) + ); + } + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + OV_DL_ERR_FAIL_COND_V_MSG( + args.node->kind != ovdl::v2script::ast::NodeKind::IdentifierValue, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected identifier, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind) + ) + ); + + return ValueExtractor::extract({ args.parser, args.node, args.out.value }); + } + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + OV_DL_ERR_FAIL_COND_V_MSG( + args.node->kind != ovdl::v2script::ast::NodeKind::StringValue, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected string, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind) + ) + ); + + return ValueExtractor::extract({ args.parser, args.node, args.out.value }); + } + }; + + template + struct ValueExtractor>> { + static Error extract(ValueExtractorArguments>> args) { + OV_DL_ERR_FAIL_COND_V_MSG( + args.node->kind != ovdl::v2script::ast::NodeKind::StringValue, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected string, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind) + ) + ); + + return ValueExtractor::extract({ args.parser, args.node, args.out.value.value }); + } + }; + + template + struct ValueExtractor>> { + static Error extract(ValueExtractorArguments>> args) { + OV_DL_ERR_FAIL_COND_V_MSG( + args.node->kind != ovdl::v2script::ast::NodeKind::StringValue, + Error::FAILED, + make_location_message( + args.parser, args.node, "Expected string, found {}", ovdl::v2script::ast::get_kind_name(args.node->kind) + ) + ); + + return ValueExtractor::extract({ args.parser, args.node, args.out.value.value }); + } + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template T> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args) { + using underlying_type = type_safe::underlying_type; + + return ValueExtractor::extract( + { args.parser, args.node, static_cast(args.out) } + ); + } + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template<> + struct ValueExtractor { + static Error extract(ValueExtractorArguments args); + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args); + }; + + template + struct ValueExtractor>> { + static Error extract(ValueExtractorArguments>> args); + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + OV_DL_ERR_FAIL_COND_V_MSG( + args.out.has_value(), + Error::FAILED, + make_location_message(args.parser, args.node, "Unexpected overwriting of set value") + ); + + T tmp; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, tmp })); + args.out = tmp; + return Error::OK; + } + }; + + template + struct ValueExtractor> { + static Error extract(ValueExtractorArguments> args) { + T tmp; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, tmp })); + args.out = tmp; + return Error::OK; + } + }; + + template + struct ValueMapper { + static constexpr std::size_t size = sizeof...(KVs); + + struct SymbolTable { + ovdl::v2script::Parser const* parser = nullptr; + std::array, size> symbols {}; + }; + + inline static thread_local SymbolTable table; + + static constexpr std::array keys() { + return { std::string_view { KVs.key }... }; + } + + static Error initialize(ValueInitializeArguments args) { + if (args.parser != table.parser) { + table.parser = args.parser; + } + + if (OV_unlikely(args.parser == nullptr)) { + return Error::OK; + } + + std::size_t i = 0; + ((table.symbols[i++] = args.parser->find_intern(KVs.key)), ...); + return Error::OK; + } + + static Error extract(ValueExtractorArguments args) { + if (OV_unlikely(args.parser == nullptr)) { + std::string_view sv; + OV_RETURN_IF_ERROR(ValueExtractor::extract({ args.parser, args.node, sv })); + + Error err = Error::FAILED; + std::size_t i = 0; + // first matching key wins + ((sv && sv == KVs.key && (args.out = KVs.value, err = Error::OK, true)) || ...); + + OV_DL_ERR_FAIL_COND_V_MSG( + err != Error::OK, + err, + make_location_message(args.parser, args.node, "Expected value: [{}], found {}", fmt::join(keys(), ", "), sv) + ); + + return Error::OK; + } + + ovdl::symbol<> value; + OV_RETURN_IF_ERROR(ValueExtractor>::extract({ args.parser, args.node, value })); + + if (OV_unlikely(args.parser != table.parser)) { + initialize({ args.parser, args.out }); + } + + Error err = Error::FAILED; + std::size_t i = 0; + // first matching key wins + ((value && value == table.symbols[i++] && (args.out = KVs.value, err = Error::OK, true)) || ...); + + OV_DL_ERR_FAIL_COND_V_MSG( + err != Error::OK, + err, + make_location_message( + args.parser, args.node, "Expected value: [{}], found {}", fmt::join(keys(), ", "), value.view() + ) + ); + + return Error::OK; + } + }; + + template<> + struct ValueExtractor : ValueMapper, KV<"no", false>> {}; + + template<> + struct ValueExtractor + : ValueMapper< + text_format_t, + KV<"left", text_format_t::left>, + KV<"right", text_format_t::right>, + KV<"justified", text_format_t::justified>, + KV<"center", text_format_t::centre>, + KV<"centre", text_format_t::centre>> {}; +} + +extern template struct OpenVic::dataloader::ValueExtractor>; +extern template struct OpenVic::dataloader::ValueExtractor>; + +extern template struct OpenVic::dataloader::ValueExtractor>; +extern template struct OpenVic::dataloader::ValueExtractor>; + +extern template struct OpenVic::dataloader::ValueExtractor>; +extern template struct OpenVic::dataloader::ValueExtractor>; + +extern template struct OpenVic::dataloader::ValueExtractor< + OpenVic::basic_colour_t>>; +extern template struct OpenVic::dataloader::ValueExtractor< + OpenVic::basic_colour_t>>; + +extern template struct OpenVic::dataloader::ValueExtractor>>>; +extern template struct OpenVic::dataloader::ValueExtractor>>>; diff --git a/tests/src/definition/dataloader/TreeTraverse.cpp b/tests/src/definition/dataloader/TreeTraverse.cpp new file mode 100644 index 000000000..d3047f185 --- /dev/null +++ b/tests/src/definition/dataloader/TreeTraverse.cpp @@ -0,0 +1 @@ +#include "openvic-simulation/definition/dataloader/TreeTraverse.hpp"