diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp index 46ca62e425..5c2dad9d7e 100644 --- a/Utilities/Config.cpp +++ b/Utilities/Config.cpp @@ -83,7 +83,44 @@ bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max) if (result < min || result > max) { - if (out) cfg_log.error("cfg::try_to_int('%s'): out of bounds (%lld..%lld)", value, min, max); + if (out) cfg_log.error("cfg::try_to_int('%s'): out of bounds (%d..%d)", value, min, max); + return false; + } + + if (out) *out = result; + return true; +} + +std::vector cfg::make_uint_range(u64 min, u64 max) +{ + return {std::to_string(min), std::to_string(max)}; +} + +bool cfg::try_to_uint64(u64* out, const std::string& value, u64 min, u64 max) +{ + u64 result; + const char* start = &value.front(); + const char* end = &value.back() + 1; + int base = 10; + + if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) + { + // Limited hex support + base = 16; + start += 2; + } + + const auto ret = std::from_chars(start, end, result, base); + + if (ret.ec != std::errc() || ret.ptr != end) + { + if (out) cfg_log.error("cfg::try_to_int('%s'): invalid integer", value); + return false; + } + + if (result < min || result > max) + { + if (out) cfg_log.error("cfg::try_to_int('%s'): out of bounds (%u..%u)", value, min, max); return false; } diff --git a/Utilities/Config.h b/Utilities/Config.h index 2bd3572ea1..3c81685d20 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -20,6 +20,12 @@ namespace cfg // Convert string to signed integer bool try_to_int64(s64* out, const std::string& value, s64 min, s64 max); + // Format min and max unsigned values + std::vector make_uint_range(u64 min, u64 max); + + // Convert string to unsigned integer + bool try_to_uint64(u64* out, const std::string& value, u64 min, u64 max); + // Internal hack bool try_to_enum_value(u64* out, decltype(&fmt_class_string::format) func, const std::string&); @@ -27,12 +33,13 @@ namespace cfg std::vector try_to_enum_list(decltype(&fmt_class_string::format) func); // Config tree entry type. - enum class type : uint + enum class type : unsigned { node = 0, // cfg::node type _bool, // cfg::_bool type _enum, // cfg::_enum type _int, // cfg::_int type + uint, // cfg::uint type string, // cfg::string type set, // cfg::set_entry type log, @@ -302,6 +309,80 @@ namespace cfg // Alias for 64 bit int using int64 = _int; + // Unsigned 32/64-bit integer entry with custom Min/Max range. + template + class uint final : public _base + { + static_assert(Min < Max, "Invalid cfg::uint range"); + + // Prefer 32 bit type if possible + using int_type = std::conditional_t; + + atomic_t m_value; + + public: + int_type def; + + // Expose range + static const u64 max = Max; + static const u64 min = Min; + + uint(node* owner, const std::string& name, int_type def = std::max(Min, 0), bool dynamic = false) + : _base(type::uint, owner, name, dynamic) + , m_value(def) + , def(def) + { + } + + operator int_type() const + { + return m_value; + } + + int_type get() const + { + return m_value; + } + + void from_default() override + { + m_value = def; + } + + std::string to_string() const override + { + return std::to_string(m_value); + } + + bool from_string(const std::string& value, bool /*dynamic*/ = false) override + { + u64 result; + if (try_to_uint64(&result, value, Min, Max)) + { + m_value = static_cast(result); + return true; + } + + return false; + } + + void set(const u64& value) + { + m_value = static_cast(value); + } + + std::vector to_list() const override + { + return make_uint_range(Min, Max); + } + }; + + // Alias for 32 bit uint + using uint32 = uint<0, UINT32_MAX>; + + // Alias for 64 bit int + using uint64 = uint<0, UINT64_MAX>; + // Simple string entry with mutex class string final : public _base {