diff --git a/src/openrct2/core/String.cpp b/src/openrct2/core/String.cpp index afdf7db582..5efccf8649 100644 --- a/src/openrct2/core/String.cpp +++ b/src/openrct2/core/String.cpp @@ -390,13 +390,13 @@ namespace OpenRCT2::String return buffer; } - std::vector split(std::string_view s, std::string_view delimiter) + std::vector split(std::string_view s, std::string_view delimiter) { if (delimiter.empty()) { - throw std::invalid_argument("delimiter can notbe empty."); + throw std::invalid_argument("delimiter can not be empty."); } - std::vector results; + std::vector results; if (s.empty()) { return results; diff --git a/src/openrct2/core/String.hpp b/src/openrct2/core/String.hpp index 9086cf5e0e..f8120f4968 100644 --- a/src/openrct2/core/String.hpp +++ b/src/openrct2/core/String.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -85,7 +86,7 @@ namespace OpenRCT2::String * Splits the given string by a delimiter and returns the values as a new string array. * @returns the number of values. */ - std::vector split(std::string_view s, std::string_view delimiter); + std::vector split(std::string_view s, std::string_view delimiter); utf8* skipBOM(utf8* buffer); const utf8* skipBOM(const utf8* buffer); @@ -113,7 +114,7 @@ namespace OpenRCT2::String std::string toUpper(std::string_view src); template - std::optional parse(std::string_view input) + inline std::optional tryParse(std::string_view input) { if (input.empty()) { @@ -128,6 +129,34 @@ namespace OpenRCT2::String return std::nullopt; } + template + inline T parse(std::string_view input) + { + if (input.empty()) + { + throw std::invalid_argument("Input is empty"); + } + T result; + auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), result); + if (ec == std::errc::invalid_argument) + { + throw std::invalid_argument("Invalid argument in conversion"); + } + if (ec == std::errc::result_out_of_range) + { + throw std::out_of_range("Result out of range"); + } + if (ec != std::errc()) + { + throw std::runtime_error("Conversion error"); + } + if (ptr != input.data() + input.size()) + { + throw std::invalid_argument("Trailing characters after number"); + } + return result; + } + /** * Returns string representation of a hexadecimal input, such as SHA256 hash */ diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index d4630b444a..a05fa25e77 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -353,12 +353,12 @@ namespace OpenRCT2 auto parts = String::split(s, ".."); if (parts.size() == 1) { - result.push_back(std::stoi(parts[0])); + result.push_back(String::parse(parts[0])); } else { - auto left = std::stoi(parts[0]); - auto right = std::stoi(parts[1]); + auto left = String::parse(parts[0]); + auto right = String::parse(parts[1]); if (left <= right) { for (auto i = left; i <= right; i++) diff --git a/test/tests/StringTest.cpp b/test/tests/StringTest.cpp index 33d75a916e..ab30c513a0 100644 --- a/test/tests/StringTest.cpp +++ b/test/tests/StringTest.cpp @@ -66,17 +66,17 @@ TEST_P(StringTest, TrimStart) TEST_F(StringTest, Split_ByComma) { auto actual = String::split("a,bb,ccc,dd", ","); - AssertVector(actual, { "a", "bb", "ccc", "dd" }); + AssertVector(actual, { "a", "bb", "ccc", "dd" }); } TEST_F(StringTest, Split_ByColonColon) { auto actual = String::split("a::bb:ccc:::::dd", "::"); - AssertVector(actual, { "a", "bb:ccc", "", ":dd" }); + AssertVector(actual, { "a", "bb:ccc", "", ":dd" }); } TEST_F(StringTest, Split_Empty) { auto actual = String::split("", "."); - AssertVector(actual, {}); + AssertVector(actual, {}); } TEST_F(StringTest, Split_ByEmpty) { @@ -294,153 +294,153 @@ TEST_F(StringTest, LogicalCompare) TEST_F(StringTest, Parse_Basic) { - auto actual = String::parse("123"); + auto actual = String::tryParse("123"); ASSERT_TRUE(actual.has_value()); ASSERT_EQ(*actual, 123); } TEST_F(StringTest, Parse_Empty) { - auto actual = String::parse(""); + auto actual = String::tryParse(""); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_Zero) { - auto actual = String::parse("0"); + auto actual = String::tryParse("0"); ASSERT_TRUE(actual.has_value()); ASSERT_EQ(*actual, 0); } TEST_F(StringTest, Parse_LeadingZero) { - auto actual = String::parse("00123"); + auto actual = String::tryParse("00123"); ASSERT_TRUE(actual.has_value()); ASSERT_EQ(*actual, 123); } TEST_F(StringTest, Parse_InvalidChar) { - auto actual = String::parse("12a3"); + auto actual = String::tryParse("12a3"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_LeadingNonDigit) { - auto actual = String::parse("a123"); + auto actual = String::tryParse("a123"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_Negative) { - auto actual = String::parse("-123"); + auto actual = String::tryParse("-123"); ASSERT_TRUE(actual.has_value()); ASSERT_EQ(*actual, -123); } TEST_F(StringTest, Parse_Overflow) { - auto actual = String::parse("2147483648"); + auto actual = String::tryParse("2147483648"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_LargeNumber) { - auto actual = String::parse("9223372036854775807"); + auto actual = String::tryParse("9223372036854775807"); ASSERT_TRUE(actual.has_value()); ASSERT_EQ(*actual, 9223372036854775807LL); } TEST_F(StringTest, Parse_FloatBasic) { - auto actual = String::parse("123.45"); + auto actual = String::tryParse("123.45"); ASSERT_TRUE(actual.has_value()); ASSERT_FLOAT_EQ(*actual, 123.45f); } TEST_F(StringTest, Parse_FloatZero) { - auto actual = String::parse("0.0"); + auto actual = String::tryParse("0.0"); ASSERT_TRUE(actual.has_value()); ASSERT_FLOAT_EQ(*actual, 0.0f); } TEST_F(StringTest, Parse_FloatScientific) { - auto actual = String::parse("1.23e4"); + auto actual = String::tryParse("1.23e4"); ASSERT_TRUE(actual.has_value()); ASSERT_FLOAT_EQ(*actual, 12300.0f); } TEST_F(StringTest, Parse_FloatInvalid) { - auto actual = String::parse("123.4a5"); + auto actual = String::tryParse("123.4a5"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_FloatLeadingDot) { - auto actual = String::parse(".123"); + auto actual = String::tryParse(".123"); ASSERT_TRUE(actual.has_value()); ASSERT_FLOAT_EQ(*actual, 0.123f); } TEST_F(StringTest, Parse_FloatNegative) { - auto actual = String::parse("-123.45"); + auto actual = String::tryParse("-123.45"); ASSERT_TRUE(actual.has_value()); ASSERT_FLOAT_EQ(*actual, -123.45f); } TEST_F(StringTest, Parse_FloatOverflow) { - auto actual = String::parse("3.4028235e39"); + auto actual = String::tryParse("3.4028235e39"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_DoubleBasic) { - auto actual = String::parse("123.456789"); + auto actual = String::tryParse("123.456789"); ASSERT_TRUE(actual.has_value()); ASSERT_DOUBLE_EQ(*actual, 123.456789); } TEST_F(StringTest, Parse_DoubleZero) { - auto actual = String::parse("0.0"); + auto actual = String::tryParse("0.0"); ASSERT_TRUE(actual.has_value()); ASSERT_DOUBLE_EQ(*actual, 0.0); } TEST_F(StringTest, Parse_DoubleScientific) { - auto actual = String::parse("1.23e10"); + auto actual = String::tryParse("1.23e10"); ASSERT_TRUE(actual.has_value()); ASSERT_DOUBLE_EQ(*actual, 12300000000.0); } TEST_F(StringTest, Parse_DoubleInvalid) { - auto actual = String::parse("123.4a5"); + auto actual = String::tryParse("123.4a5"); ASSERT_FALSE(actual.has_value()); } TEST_F(StringTest, Parse_DoubleLeadingDot) { - auto actual = String::parse(".123"); + auto actual = String::tryParse(".123"); ASSERT_TRUE(actual.has_value()); ASSERT_DOUBLE_EQ(*actual, 0.123); } TEST_F(StringTest, Parse_DoubleNegative) { - auto actual = String::parse("-123.45"); + auto actual = String::tryParse("-123.45"); ASSERT_TRUE(actual.has_value()); ASSERT_DOUBLE_EQ(*actual, -123.45); } TEST_F(StringTest, Parse_DoubleOverflow) { - auto actual = String::parse("1.7976931348623158e309"); + auto actual = String::tryParse("1.7976931348623158e309"); ASSERT_FALSE(actual.has_value()); }