Files
espurna/code/espurna/settings_convert.cpp
2024-05-15 23:52:36 +03:00

479 lines
10 KiB
C++

/*
Part of SETTINGS MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2019-2023 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include <Arduino.h>
#include "utils.h"
#include "settings_convert.h"
#include "settings_helpers.h"
namespace espurna {
namespace settings {
namespace internal {
namespace duration_convert {
namespace {
// Input is always normalized to Pair, specific units are converted on demand
constexpr auto MicrosecondsPerSecond =
duration::Microseconds{ duration::Microseconds::period::den };
void adjust_microseconds(Pair& pair) {
if (pair.microseconds >= MicrosecondsPerSecond) {
pair.seconds += duration::Seconds{ 1 };
pair.microseconds -= MicrosecondsPerSecond;
}
}
Pair from_chrono_duration(duration::Microseconds microseconds) {
Pair out{};
while (microseconds > MicrosecondsPerSecond) {
out.seconds += duration::Seconds{ 1 };
microseconds -= MicrosecondsPerSecond;
}
out.microseconds += microseconds;
adjust_microseconds(out);
return out;
}
constexpr auto MillisecondsPerSecond =
duration::Milliseconds{ duration::Milliseconds::period::den };
Pair from_chrono_duration(duration::Milliseconds milliseconds) {
Pair out{};
while (milliseconds >= MillisecondsPerSecond) {
out.seconds += duration::Seconds{ 1 };
milliseconds -= MillisecondsPerSecond;
}
const auto microseconds =
std::chrono::duration_cast<duration::Microseconds>(milliseconds);
out.microseconds += microseconds;
adjust_microseconds(out);
return out;
}
Pair& operator+=(Pair& lhs, const Pair& rhs) {
lhs.seconds += rhs.seconds;
lhs.microseconds += rhs.microseconds;
adjust_microseconds(lhs);
return lhs;
}
template <typename T>
Pair& operator+=(Pair&, T);
template <>
Pair& operator+=(Pair& result, duration::Microseconds microseconds) {
result += from_chrono_duration(microseconds);
return result;
}
template <>
Pair& operator+=(Pair& result, duration::Milliseconds milliseconds) {
result += from_chrono_duration(milliseconds);
return result;
}
template <>
Pair& operator+=(Pair& result, duration::Hours hours) {
result.seconds += std::chrono::duration_cast<duration::Seconds>(hours);
return result;
}
template <>
Pair& operator+=(Pair& result, duration::Minutes minutes) {
result.seconds += std::chrono::duration_cast<duration::Seconds>(minutes);
return result;
}
template <>
Pair& operator+=(Pair& result, duration::Seconds seconds) {
result.seconds += seconds;
return result;
}
// Besides decimal or raw input with the specified ratio,
// string parser also supports type specifiers at the end of decimal number
enum class Type {
Unknown,
Seconds,
Minutes,
Hours,
};
bool validNextType(Type lhs, Type rhs) {
switch (lhs) {
case Type::Unknown:
return true;
case Type::Hours:
return (rhs == Type::Minutes)
|| (rhs == Type::Seconds);
case Type::Minutes:
return (rhs == Type::Seconds);
case Type::Seconds:
break;
}
return false;
}
} // namespace
Result parse(StringView view, int num, int den) {
Result out;
out.ok = false;
String token;
Type last { Type::Unknown };
Type type { Type::Unknown };
const char* ptr { view.begin() };
if (!view.begin() || !view.length()) {
goto output;
}
loop:
while (ptr != view.end()) {
switch (*ptr) {
case '0'...'9':
token += (*ptr);
++ptr;
break;
case 'h':
if (validNextType(last, Type::Hours)) {
type = Type::Hours;
goto update_spec;
}
goto reset;
case 'm':
if (validNextType(last, Type::Minutes)) {
type = Type::Minutes;
goto update_spec;
}
goto reset;
case 's':
if (validNextType(last, Type::Seconds)) {
type = Type::Seconds;
goto update_spec;
}
goto reset;
case 'e':
case 'E':
goto read_floating_exponent;
case ',':
case '.':
if (out.ok) {
goto reset;
}
goto read_floating;
default:
goto reset;
}
}
if (token.length()) {
goto update_decimal;
}
goto output;
update_floating:
{
// only seconds and up, anything down of milli does not make sense here
if (den > 1) {
goto reset;
}
char* endp { nullptr };
auto value = strtod(token.c_str(), &endp);
if (endp && (endp != token.c_str()) && endp[0] == '\0') {
using Seconds = std::chrono::duration<float, std::ratio<1> >;
const auto seconds = Seconds(num * value);
const auto milliseconds =
std::chrono::duration_cast<duration::Milliseconds>(seconds);
out.value += milliseconds;
out.ok = true;
goto output;
}
goto reset;
}
update_decimal:
{
const auto result = parseUnsigned(token, 10);
if (result.ok) {
// num and den are constexpr and bound to ratio types, so duration cast has to happen manually
if ((num == 1) && (den == 1)) {
out.value += duration::Seconds{ result.value };
} else if ((num == 1) && (den > 1)) {
out.value += duration::Seconds{ result.value / den };
out.value += duration::Microseconds{ result.value % den * duration::Microseconds::period::den / den };
} else if ((num > 1) && (den == 1)) {
out.value += duration::Seconds{ result.value * num };
} else {
goto reset;
}
out.ok = true;
goto output;
}
goto reset;
}
update_spec:
last = type;
++ptr;
if (type != Type::Unknown) {
const auto result = parseUnsigned(token, 10);
if (result.ok) {
switch (type) {
case Type::Hours:
out.value += duration::Hours{ result.value };
break;
case Type::Minutes:
out.value += duration::Minutes{ result.value };
break;
case Type::Seconds:
out.value += duration::Seconds{ result.value };
break;
case Type::Unknown:
goto reset;
}
out.ok = true;
type = Type::Unknown;
token = "";
goto loop;
}
}
goto reset;
read_floating:
switch (*ptr) {
case ',':
case '.':
token += '.';
++ptr;
break;
default:
goto reset;
}
while (ptr != view.end()) {
switch (*ptr) {
case '0'...'9':
token += (*ptr);
break;
case 'e':
case 'E':
goto read_floating_exponent;
case ',':
case '.':
goto reset;
}
++ptr;
}
goto update_floating;
read_floating_exponent:
{
token += (*ptr);
++ptr;
bool sign { false };
while (ptr != view.end()) {
switch (*ptr) {
case '-':
case '+':
if (sign) {
goto reset;
}
sign = true;
token += (*ptr);
++ptr;
break;
case '0'...'9':
token += (*ptr);
++ptr;
break;
default:
goto reset;
}
}
goto update_floating;
}
reset:
out.ok = false;
output:
return out;
}
} // namespace duration_convert
template <>
duration::Microseconds convert(const String& value) {
return duration_convert::unchecked_parse<duration::Microseconds>(value);
}
template <>
duration::Milliseconds convert(const String& value) {
return duration_convert::unchecked_parse<duration::Milliseconds>(value);
}
template <>
duration::Seconds convert(const String& value) {
return duration_convert::unchecked_parse<duration::Seconds>(value);
}
template <>
duration::Minutes convert(const String& value) {
return duration_convert::unchecked_parse<duration::Minutes>(value);
}
template <>
duration::Hours convert(const String& value) {
return duration_convert::unchecked_parse<duration::Hours>(value);
}
template <>
float convert(const String& value) {
return strtod(value.c_str(), nullptr);
}
template <>
double convert(const String& value) {
return strtod(value.c_str(), nullptr);
}
template <>
signed char convert(const String& value) {
return value.toInt();
}
template <>
short convert(const String& value) {
return value.toInt();
}
template <>
int convert(const String& value) {
return value.toInt();
}
template <>
long convert(const String& value) {
return value.toInt();
}
template <>
bool convert(const String& value) {
if (value.length()) {
if ((value == "0")
|| (value == "n")
|| (value == "no")
|| (value == "false")
|| (value == "off")) {
return false;
}
return (value == "1")
|| (value == "y")
|| (value == "yes")
|| (value == "true")
|| (value == "on");
}
return false;
}
template <>
uint32_t convert(const String& value) {
return parseUnsigned(value).value;
}
String serialize(uint32_t value, int base) {
return formatUnsigned(value, base);
}
template <>
unsigned long convert(const String& value) {
return convert<unsigned int>(value);
}
template <>
unsigned short convert(const String& value) {
return convert<unsigned long>(value);
}
template <>
unsigned char convert(const String& value) {
return convert<unsigned long>(value);
}
String serialize(duration::Seconds value) {
return serialize(value.count());
}
String serialize(duration::Milliseconds value) {
return serialize(value.count());
}
String serialize(duration::Minutes value) {
return serialize(value.count()) + 'm';
}
String serialize(duration::Hours value) {
return serialize(value.count()) + 'h';
}
} // namespace internal
} // namespace settings
} // namespace espurna