From d32196ab2a4d787f93db0bfd3500572946ea180d Mon Sep 17 00:00:00 2001 From: Vaxry Date: Tue, 17 Mar 2026 14:52:42 -0400 Subject: [PATCH] string/numeric: add hex parsing --- include/hyprutils/string/Numeric.hpp | 19 +++++++++++ tests/string/Numeric.cpp | 51 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/include/hyprutils/string/Numeric.hpp b/include/hyprutils/string/Numeric.hpp index ec1188c..a1239b4 100644 --- a/include/hyprutils/string/Numeric.hpp +++ b/include/hyprutils/string/Numeric.hpp @@ -21,6 +21,25 @@ namespace Hyprutils::String { return std::unexpected(NUMERIC_PARSE_BAD); T value{}; + + if constexpr (std::integral) { + if (sv.size() >= 2 && sv[0] == '0' && (sv[1] == 'x' || sv[1] == 'X')) { + if (sv.size() == 2) + return std::unexpected(NUMERIC_PARSE_BAD); + const auto hex = sv.substr(2); + const auto [ptr, ec] = std::from_chars(hex.data(), hex.data() + hex.size(), value, 16); + + if (ec == std::errc::invalid_argument) + return std::unexpected(NUMERIC_PARSE_BAD); + if (ec == std::errc::result_out_of_range) + return std::unexpected(NUMERIC_PARSE_OUT_OF_RANGE); + if (ptr != hex.data() + hex.size()) + return std::unexpected(NUMERIC_PARSE_GARBAGE); + + return value; + } + } + const auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), value); if (ec == std::errc::invalid_argument) diff --git a/tests/string/Numeric.cpp b/tests/string/Numeric.cpp index 3fd5f26..26d8ce4 100644 --- a/tests/string/Numeric.cpp +++ b/tests/string/Numeric.cpp @@ -90,3 +90,54 @@ TEST(Numeric, unsignedTypes) { ASSERT_TRUE(r2.has_value()); EXPECT_EQ(*r2, 0u); } + +TEST(Numeric, hexSuccess) { + auto r = strToNumber("0xAF23"); + ASSERT_TRUE(r.has_value()); + EXPECT_EQ(*r, 0xAF23); + + auto r2 = strToNumber("0xFF"); + ASSERT_TRUE(r2.has_value()); + EXPECT_EQ(*r2, 0xFFu); + + auto r3 = strToNumber("0xDEADBEEF"); + ASSERT_TRUE(r3.has_value()); + EXPECT_EQ(*r3, 0xDEADBEEFu); + + // uppercase X prefix + auto r4 = strToNumber("0XFF"); + ASSERT_TRUE(r4.has_value()); + EXPECT_EQ(*r4, 0xFF); + + // lowercase hex digits + auto r5 = strToNumber("0xdeadbeef"); + ASSERT_TRUE(r5.has_value()); + EXPECT_EQ(*r5, 0xDEADBEEFu); + + // zero value + auto r6 = strToNumber("0x0"); + ASSERT_TRUE(r6.has_value()); + EXPECT_EQ(*r6, 0); +} + +TEST(Numeric, hexErrors) { + // incomplete prefix (just "0x") + auto r = strToNumber("0x"); + ASSERT_FALSE(r.has_value()); + EXPECT_EQ(r.error(), NUMERIC_PARSE_BAD); + + // garbage after valid hex digits + auto r2 = strToNumber("0xFF_ZZ"); + ASSERT_FALSE(r2.has_value()); + EXPECT_EQ(r2.error(), NUMERIC_PARSE_GARBAGE); + + // out of range for type + auto r3 = strToNumber("0xFFF"); + ASSERT_FALSE(r3.has_value()); + EXPECT_EQ(r3.error(), NUMERIC_PARSE_OUT_OF_RANGE); + + // floats do not accept hex prefix + auto r4 = strToNumber("0xFF"); + ASSERT_FALSE(r4.has_value()); + EXPECT_EQ(r4.error(), NUMERIC_PARSE_GARBAGE); +}