Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkcgen <kcgen@users.noreply.github.com>2022-10-12 23:54:28 +0300
committerkcgen <1557255+kcgen@users.noreply.github.com>2022-10-14 05:18:46 +0300
commitcab7dd12d78f08f96231334e7594ace4d5efdbfa (patch)
tree0c68534fbfa4d3492616f488402c71ed732dfec9
parentfef3b9c479175370f81cf650581ec386a4ddb1eb (diff)
Add value and percentage parsing helpers
-rw-r--r--include/math_utils.h16
-rw-r--r--include/string_utils.h61
-rw-r--r--src/misc/string_utils.cpp42
-rw-r--r--tests/string_utils_tests.cpp73
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