* @brief Parse refresh rate value from the string. * @param input String to be parsed. * @param output Reference to output variable to fill in. * @param allow_decimal_point Specify whether the decimal point is allowed or not. * @returns True on successful parsing (empty string allowed), false otherwise. * * @examples * std::optional refresh_rate;
| 196 | * @examples_end |
| 197 | */ |
| 198 | bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) { |
| 199 | static const auto is_zero {[](const auto &character) { |
| 200 | return character == '0'; |
| 201 | }}; |
| 202 | const std::string trimmed_input {boost::algorithm::trim_copy(input)}; |
| 203 | const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"}; |
| 204 | |
| 205 | if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) { |
| 206 | try { |
| 207 | // Here we are trimming zeros from the string to possibly reduce out of bounds case |
| 208 | std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)}; |
| 209 | if (trimmed_match_1.empty()) { |
| 210 | trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one |
| 211 | } |
| 212 | |
| 213 | std::string trimmed_match_2; |
| 214 | if (allow_decimal_point && match[2].matched) { |
| 215 | trimmed_match_2 = boost::algorithm::trim_right_copy_if(match[2].str(), is_zero); |
| 216 | } |
| 217 | |
| 218 | if (!trimmed_match_2.empty()) { |
| 219 | // We have a decimal point and will have to split it into numerator and denominator. |
| 220 | // For example: |
| 221 | // 59.995: |
| 222 | // numerator = 59995 |
| 223 | // denominator = 1000 |
| 224 | |
| 225 | // We are essentially removing the decimal point here: 59.995 -> 59995 |
| 226 | const std::string numerator_str {trimmed_match_1 + trimmed_match_2}; |
| 227 | const auto numerator {stou(numerator_str)}; |
| 228 | |
| 229 | // Here we are counting decimal places and calculating denominator: 10^decimal_places |
| 230 | const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))}; |
| 231 | |
| 232 | output = Rational {numerator, denominator}; |
| 233 | } else { |
| 234 | // We do not have a decimal point, just a valid number. |
| 235 | // For example: |
| 236 | // 60: |
| 237 | // numerator = 60 |
| 238 | // denominator = 1 |
| 239 | output = Rational {stou(trimmed_match_1), 1}; |
| 240 | } |
| 241 | return true; |
| 242 | } catch (const std::out_of_range &) { |
| 243 | BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range)."; |
| 244 | } catch (const std::exception &err) { |
| 245 | BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n" |
| 246 | << err.what(); |
| 247 | } |
| 248 | } else { |
| 249 | if (trimmed_input.empty()) { |
| 250 | output = std::nullopt; |
| 251 | return true; |
| 252 | } |
| 253 | |
| 254 | BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ". Must have a pattern of " << (allow_decimal_point ? R"("123" or "123.456")" : R"("123")") << "!"; |
| 255 | } |
no test coverage detected