diff --git a/cxx/CMakeLists.txt b/cxx/CMakeLists.txt index f010147..e20c550 100644 --- a/cxx/CMakeLists.txt +++ b/cxx/CMakeLists.txt @@ -13,4 +13,5 @@ set(COMM_PROTO_LIB_SRC comm_proto.h comm_proto.cpp) set(COMM_PROTO_LIB comm_proto) add_library(${COMM_PROTO_LIB} STATIC ${COMM_PROTO_LIB_SRC} control_proto.h - control_proto.cpp) + control_proto.cpp + utils.h) diff --git a/cxx/utils.h b/cxx/utils.h new file mode 100644 index 0000000..2430a35 --- /dev/null +++ b/cxx/utils.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include + +namespace mcc::utils +{ + +static const std::regex decimalNumberRx{" *[-+]?([0-9]*[.])?[0-9]+([eE][-+]?[0-9]+)? *"}; +static const std::regex sexagesimalReprRx{" *[-+]?[0-9]{1,2}:[0-9]{1,2}:([0-9]{0,2}[.])?[0-9]+ *"}; + +enum class TrimType { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH }; + +template +static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH) + requires std::same_as>> +{ + auto is_space = [](const auto& ch) { return ch == ' '; }; + + auto end = std::forward(r).end(); + + auto f1 = std::forward(r).begin(); + + if (type != TrimType::TRIM_RIGHT) { + // look for the first non-space symbol + f1 = std::ranges::find_if_not(std::forward(r), is_space); + if (f1 == end) { // all are spaces! + return std::string_view(); + } + } + + auto f2 = end; + + if (type != TrimType::TRIM_LEFT) { + auto f3 = f1; + + do { + f2 = std::ranges::find_if(++f3, end, is_space); + if (f2 == end) + break; + f3 = std::ranges::find_if_not(f2 + 1, end, is_space); + } while (f3 != end); + } + + return std::string_view(f1, f2); +} + + +template +std::optional numFromStr(R&& r) + requires((std::integral || std::floating_point) && + std::same_as>>) +{ + T val; + const char* end_ptr = &*r.end(); + + if constexpr (std::integral) { + auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val); + if (cvt_res.ec != std::errc()) { + return std::nullopt; + } else if (cvt_res.ptr != end_ptr) { + return std::nullopt; + } + } else { +#ifdef _LIBCPP_VERSION // clang's libc++ does not have floating-point overloads for std::from_chars + std::string s{str.begin(), str.end()}; + size_t pos; + + try { + if constexpr (std::same_as) { + val = std::stof(s, &pos); + } else if constexpr (std::same_as) { + val = std::stod(s, &pos); + } else { + val = std::stold(s, &pos); + } + } catch (...) { + return std::nullopt; + } + + if (pos != s.size()) { + return std::nullopt; + } +#else + auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val); + + if (cvt_res.ec != std::errc()) { + return std::nullopt; + } else if (cvt_res.ptr != end_ptr) { + return std::nullopt; + } +#endif + } + + return val; +} + +template +static std::optional parsAngleString(R&& r, bool hms = false) + requires std::same_as>> +{ + auto str = trimSpaces(std::forward(r)); + + bool ok = std::regex_match(str.begin(), str.end(), decimalNumberRx); + + if (ok) { + return numFromStr(str); + } + + ok = std::regex_match(str.begin(), str.end(), sexagesimalReprRx); + + if (ok) { + auto str = trimSpaces(std::forward(r)); + auto parts = std::views::split(str, ":"); + + double val; + + double p1 = numFromStr(parts[0]); + val = std::abs(p1); + val += numFromStr(parts[1]) / 60.0; + val += numFromStr(parts[2]) / 3600.0; + if (p1 < 0) { + val = -val; + } + + if (hms) { + val *= 15.0; + } + + return val; + } + + + return std::nullopt; +} + +} // namespace mcc::utils