diff options
author | kcgen <kcgen@users.noreply.github.com> | 2022-10-12 23:54:28 +0300 |
---|---|---|
committer | kcgen <1557255+kcgen@users.noreply.github.com> | 2022-10-14 05:18:46 +0300 |
commit | cab7dd12d78f08f96231334e7594ace4d5efdbfa (patch) | |
tree | 0c68534fbfa4d3492616f488402c71ed732dfec9 | |
parent | fef3b9c479175370f81cf650581ec386a4ddb1eb (diff) |
Add value and percentage parsing helpers
-rw-r--r-- | include/math_utils.h | 16 | ||||
-rw-r--r-- | include/string_utils.h | 61 | ||||
-rw-r--r-- | src/misc/string_utils.cpp | 42 | ||||
-rw-r--r-- | tests/string_utils_tests.cpp | 73 |
4 files changed, 190 insertions, 2 deletions
diff --git a/include/math_utils.h b/include/math_utils.h index 849921cde..40e8f399d 100644 --- a/include/math_utils.h +++ b/include/math_utils.h @@ -137,6 +137,20 @@ inline double gain_to_decibel(const double gain) return 20.0 * log(gain) / log(10.0); } +// A wrapper to convert a scalar gain to a percentage. +// This avoids having a bunch of magic *100.0 throughout the code. +constexpr float gain_to_percentage(const float gain) +{ + return gain * 100.0f; +} + +// A wrapper to convert a percentage into a scalar gain. +// This avoids having a bunch of magic /100.0 throughout the code. +constexpr float percentage_to_gain(const float percentage) +{ + return percentage / 100.0f; +} + template <typename T> constexpr T lerp(const T a, const T b, const T t) { @@ -171,5 +185,5 @@ template float remap<float>(const float in_min, const float in_max, template double remap<double>(const double in_min, const double in_max, const double out_min, const double out_max, const double v); - + #endif diff --git a/include/string_utils.h b/include/string_utils.h index 6f52c5698..7b6ea9a63 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -26,6 +26,7 @@ #include <cassert> #include <cstdarg> #include <cstring> +#include <optional> #include <string> #include <vector> @@ -212,4 +213,64 @@ bool UTF8_RenderForDos(const std::string &str_in, std::string &str_out, const uint16_t code_page = 0); +// Parse a value from the string, clamp the result within the given min and max +// values, and return it as a float. This API should give us enough numerical +// range and accuracy for any text-based inputs. +// +// For example: +// - parse_value("101", 0, 100) return 100.0f. +// - parse_value("x10", 0, 100) return empty. +// - parse_value("txt", 0, 100) return empty. +// - parse_value("", 0, 100) return empty. +// +// To use it, check if the result then access it: +// const auto val = parse_value(s, ...); +// if (val) +// do_something(*val) +// else +// log_warning("%s was invalid", s.c_str()); +// +// Alternatively, scope the value inside the if/else +// if (const auto v = parse_value(s, ...); v) +// do_something(*v) +// else +// log_warning("%s was invalid", s.c_str()); +// +std::optional<float> parse_value(const std::string &s, const float min_value, + const float max_value); + +// parse_value clamped between 0 and 100 +std::optional<float> parse_percentage(const std::string &s); + +// Parse a value from a character-prefixed string, clamp the result within the +// given min and max values, and return it as a float. This API should give us +// enough numerical range and accuracy for any text-based inputs. +// +// For example: +// - parse_prefixed_value('x', "x101", 0, 100) return 100.0f. +// - parse_prefixed_value('X', "x101", 0, 100) return 100.0f. +// - parse_prefixed_value('y', "x101", 0, 100) return empty. +// - parse_prefixed_value('y', "1000", 0, 100) return empty. +// - parse_prefixed_value('y', "text", 0, 100) return empty. +// +// To use it, check if the result then access it: +// const auto val = parse_prefixed_value(...); +// if (val) +// do_something(*val); +// else +// log_warning("%s was invalid", s.c_str()); +// +// Alternatively, scope the value inside the if/else +// if (const auto v = parse_prefixed_value(...); v) +// do_something(*v) +// else +// log_warning("%s was invalid", s.c_str()); +// +std::optional<float> parse_prefixed_value(const char prefix, const std::string &s, + const float min_value, + const float max_value); + +// parse_prefixed_value clamped between 0 and 100 +std::optional<float> parse_prefixed_percentage(const char prefix, const std::string &s); + #endif diff --git a/src/misc/string_utils.cpp b/src/misc/string_utils.cpp index 717c1f9ba..5e2074945 100644 --- a/src/misc/string_utils.cpp +++ b/src/misc/string_utils.cpp @@ -21,8 +21,9 @@ #include "string_utils.h" #include <algorithm> -#include <vector> +#include <stdexcept> #include <string> +#include <vector> bool is_hex_digits(const std::string_view s) noexcept { @@ -233,3 +234,42 @@ void clear_language_if_default(std::string &l) l.clear(); } } + +std::optional<float> parse_value(const std::string &s, const float min_value, + const float max_value) +{ + // parse_value can check if a string holds a number (or not), so we expect + // exceptions and return an empty result to indicate conversion status. + try { + return std::clamp(std::stof(s), min_value, max_value); + // Note: stof can throw invalid_argument and out_of_range + } catch (const std::invalid_argument &) { + // do nothing, we expect these + } catch (const std::out_of_range &) { + // do nothing, we expect these + } + return {}; // empty +} + +std::optional<float> parse_percentage(const std::string &s) +{ + constexpr auto min_percentage = 0.0f; + constexpr auto max_percentage = 100.0f; + return parse_value(s, min_percentage, max_percentage); +} + +std::optional<float> parse_prefixed_value(const char prefix, const std::string &s, + const float min_value, const float max_value) +{ + if (s.size() <= 1 || !ciequals(s[0], prefix)) + return {}; + + return parse_value(s.substr(1), min_value, max_value); +} + +std::optional<float> parse_prefixed_percentage(const char prefix, const std::string &s) +{ + constexpr auto min_percentage = 0.0f; + constexpr auto max_percentage = 100.0f; + return parse_prefixed_value(prefix, s, min_percentage, max_percentage); +} diff --git a/tests/string_utils_tests.cpp b/tests/string_utils_tests.cpp index f217c4829..08de4598e 100644 --- a/tests/string_utils_tests.cpp +++ b/tests/string_utils_tests.cpp @@ -264,5 +264,78 @@ TEST(Split, Empty) EXPECT_EQ(split(" "), empty); } +TEST(ParseValue, Valid) +{ + // negatives + EXPECT_EQ(*parse_value("-10000", -11000, 0), -10000.0f); + EXPECT_EQ(*parse_value("-0.1", -1, 0), -0.1f); + EXPECT_EQ(*parse_value("-0.0001", -1, 0), -0.0001f); + EXPECT_EQ(*parse_value("-0.0", -1, 1), -0.0f); + EXPECT_EQ(*parse_value("0", -1, 1), 0.0f); + + // positives + EXPECT_EQ(*parse_value("0.0", -1, 1), 0.0f); + EXPECT_EQ(*parse_value("0.0001", -1, 1), 0.0001f); + EXPECT_EQ(*parse_value("0.1", -1, 1), 0.1f); + EXPECT_EQ(*parse_value("10000", 0, 11000), 10000.0f); +} + +TEST(ParsePercentage, Valid) +{ + EXPECT_EQ(*parse_percentage("-100"), 0.0f); + EXPECT_EQ(*parse_percentage("0"), 0.0f); + EXPECT_EQ(*parse_percentage("1"), 1.0f); + EXPECT_EQ(*parse_percentage("50"), 50.0f); + EXPECT_EQ(*parse_percentage("100"), 100.0f); + EXPECT_EQ(*parse_percentage("1000"), 100.0f); +} + +TEST(ParseBoth, Invalid) +{ + std::optional<float> empty = {}; + EXPECT_EQ(parse_value("sfafsd", 0, 1), empty); + EXPECT_EQ(parse_value("", 0, 1), empty); + EXPECT_EQ(parse_percentage("dfsfsdf"), empty); + EXPECT_EQ(parse_percentage(""), empty); +} + +TEST(ParsePrefixedValue, Valid) +{ + // negatives + EXPECT_EQ(*parse_prefixed_value('a', "a-10000", -10000, 0), -10000.0f); + EXPECT_EQ(*parse_prefixed_value('b', "b-0.1", -1, 1), -0.1f); + EXPECT_EQ(*parse_prefixed_value('c', "c-0.0001", -1, 1), -0.0001f); + EXPECT_EQ(*parse_prefixed_value('d', "d-0.0", -1, 1), -0.0f); + EXPECT_EQ(*parse_prefixed_value('e', "e0", 0, 1), 0.0f); + + // positives + EXPECT_EQ(*parse_prefixed_value('f', "f0.0", 0, 1), 0.0f); + EXPECT_EQ(*parse_prefixed_value('g', "g0.0001", 0, 1), 0.0001f); + EXPECT_EQ(*parse_prefixed_value('h', "h0.1", 0, 1), 0.1f); + EXPECT_EQ(*parse_prefixed_value('i', "i10000", 0, 11000), 10000.0f); +} + +TEST(ParsePrefixedPercentage, Valid) +{ + EXPECT_EQ(*parse_prefixed_percentage('u', "u-100"), 0.0f); + EXPECT_EQ(*parse_prefixed_percentage('v', "v0"), 0.0f); + EXPECT_EQ(*parse_prefixed_percentage('w', "w1"), 1.0f); + EXPECT_EQ(*parse_prefixed_percentage('x', "x50"), 50.0f); + EXPECT_EQ(*parse_prefixed_percentage('y', "y100"), 100.0f); + EXPECT_EQ(*parse_prefixed_percentage('z', "z1000"), 100.0f); +} + +TEST(ParsePrefixedBoth, Invalid) +{ + std::optional<float> empty = {}; + EXPECT_EQ(parse_prefixed_value('a', "b-10000", 0, 1), empty); + EXPECT_EQ(parse_prefixed_percentage('z', "y1000"), empty); + EXPECT_EQ(parse_prefixed_value('a', "-10000", 0, 1), empty); + EXPECT_EQ(parse_prefixed_percentage('z', "1000"), empty); + EXPECT_EQ(parse_prefixed_value('a', "", 0, 1), empty); + EXPECT_EQ(parse_prefixed_percentage('z', ""), empty); + EXPECT_EQ(parse_prefixed_value(' ', "----", 0, 1), empty); + EXPECT_EQ(parse_prefixed_percentage(' ', ""), empty); +} } // namespace |