#pragma once #include #include #include #include #include #include #include #include #include "mcc_traits.h" 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]+ *"}; constexpr static bool isEqual(std::floating_point auto const& v1, std::floating_point auto const& v2) { constexpr auto eps = std::numeric_limits>::epsilon(); return std::fabs(v1 - v2) <= eps * std::fmax(std::fabs(v1), std::fabs(v2)); } enum class TrimType { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH }; template constexpr 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); } constexpr static std::string_view trimSpaces(const char* r, TrimType type = TrimType::TRIM_BOTH) { return trimSpaces(std::string_view(r), type); } 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, std::string_view(":")); double val; double p1 = numFromStr(*parts.begin()).value(); val = std::abs(p1); double dv = 60.0; for (auto const& v : parts | std::views::drop(1)) { val += numFromStr(v).value() / dv; dv *= 60.0; } // 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; } static std::optional parsAngleString(const char* s, bool hms = false) { return parsAngleString(std::span{s, std::strlen(s)}, hms); } static constexpr auto deg2radCoeff = std::numbers::pi / 180.0; // radians to degrees template static double rad2deg(double ang) { auto r = ang / deg2radCoeff; if constexpr (NORM) { return std::fmod(r, 360.0); } else { return r; } } template R, bool NORM = false> static R rad2deg_str(double ang, int prec = 6) { R res; std::string fmt = "{:0." + std::to_string(prec) + "f}"; auto degs = rad2deg(ang); std::vformat_to(std::back_inserter(res), {fmt.begin(), fmt.end()}, std::make_format_args(degs)); return res; } template static std::string rad2deg_str(double ang, int prec = 6) { return rad2deg_str(ang, prec); } template R, bool NORM = false> static R rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6) { R res = rad2deg_str(ang1, prec1); R r = rad2deg_str(ang2, prec2); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6) { return rad2deg_str(ang1, ang2, delim, prec1, prec2); } // radians to sexagesimal template R, bool NORM = false> static R rad2sxg(double ang, bool hms = false, int prec = 2) { R res; if (!std::isfinite(ang)) { std::vformat_to(std::back_inserter(res), "{}", std::make_format_args(ang)); return res; } std::string fmt = "{:02.0f}:{:02.0f}:{:0" + std::to_string(prec + 3) + "." + std::to_string(prec) + "f}"; // std::string fmt = "{:02.0f}:{:02.0f}:{:02." + std::to_string(prec) + "f}"; auto degs = rad2deg(std::abs(ang)); if (hms) { degs /= 15.0; } auto term = 10.0; for (int i = 1; i < prec; ++i) { term *= 10.0; } auto d = std::trunc(degs); auto s = (degs - d) * 60.0; auto m = std::trunc(s); s = (s - m) * 60.0; // round to given precision s = std::round(s * term) / term; if (isEqual(s, 60.0)) { m += 1.0; s = 0.0; if (isEqual(m, 60.0)) { d += 1.0; m = 0.0; } } if (ang < 0) { std::ranges::copy(std::string_view("-"), std::back_inserter(res)); } std::vformat_to(std::back_inserter(res), std::string_view{fmt.begin(), fmt.end()}, std::make_format_args(d, m, s)); return res; } template static std::string rad2sxg(double ang, bool hms = false, int prec = 2) { return rad2sxg(ang, hms, prec); } template R, bool NORM = false> static R RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1) { R res = rad2sxg(ra, true, ra_prec); R r = rad2sxg(dec, false, dec_prec); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1) { return RADEC_rad2sxg(ra, dec, delim, ra_prec, dec_prec); } template R, bool NORM = false> static R AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1) { R res = rad2sxg(az, false, az_prec); R r = rad2sxg(zd, false, zd_prec); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1) { return AZZD_rad2sxg(az, zd, delim, az_prec, zd_prec); } // string to pair of angles // " 12.3453467,102.4345346" // "12:43:23.423, 102:43:12.124" // " 12.3453467, 102:43:12.124 " template std::pair parseAnglePair(R&& str, bool hms1 = false, bool hms2 = false, std::string_view delim = ",") { std::pair res{std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; auto found1 = std::ranges::search(std::forward(str), delim); if (found1.empty()) { return res; } std::string s1, s2; std::ranges::copy(str.begin(), found1.begin(), std::back_inserter(s1)); auto rem_len = std::distance(found1.end(), str.end()); if (!rem_len) { return res; } auto found2 = std::ranges::search(found1.end(), str.end(), delim.begin(), delim.end()); if (found2.empty()) { std::ranges::copy(found1.end(), str.end(), std::back_inserter(s2)); } else { std::ranges::copy(found1.end(), found2.end(), std::back_inserter(s2)); } auto vo1 = parsAngleString(s1, hms1); if (vo1) { auto vo2 = parsAngleString(s2, hms2); if (vo2) { res = {vo1.value(), vo2.value()}; } } return res; } template static constexpr size_t FNV1aHash(const R& r) { static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4, "ONLY FOR 32 or 64-bit size_t!!!"); size_t hash = 0, prime = 0; if constexpr (sizeof(size_t) == 8) { // 64-bit prime = 1099511628211UL; hash = 14695981039346656037UL; } else if constexpr (sizeof(size_t) == 4) { // 32-bit prime = 16777619; hash = 2166136261; } for (const char& ch : r) { hash ^= ch; hash *= prime; } return hash; } static constexpr size_t FNV1aHash(std::forward_iterator auto begin, std::sentinel_for auto end) requires std::same_as>, char> { static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4, "ONLY FOR 32 or 64-bit size_t!!!"); size_t hash = 0, prime = 0; if constexpr (sizeof(size_t) == 8) { // 64-bit prime = 1099511628211UL; hash = 14695981039346656037UL; } else if constexpr (sizeof(size_t) == 4) { // 32-bit prime = 16777619; hash = 2166136261; } for (auto it = begin; it != end; ++it) { hash ^= *it; hash *= prime; } return hash; } class MccSimpleDeserializer { public: static constexpr std::string_view RANGE_DELIM_SEQ = ","; MccSimpleDeserializer() : _rangeDelim(RANGE_DELIM_SEQ) {} template MccSimpleDeserializer(R&& r) : MccSimpleDeserializer() { setRangeDelim(std::forward(r)); } template MccSimpleDeserializer& setRangeDelim(R&& r) { if (std::ranges::size(std::forward(r))) { _rangeDelim.clear(); std::ranges::copy(std::forward(r), std::back_inserter(_rangeDelim)); } return *this; } template R getRangeDelim() const { R r; std::ranges::copy(_rangeDelim, std::back_inserter(r)); return r; } std::string getRangeDelim() const { return getRangeDelim(); } template std::error_code operator()(IR&& bytes, VT& value) { std::error_code ret{}; if constexpr (std::is_arithmetic_v) { auto v = mcc::utils::numFromStr(trimSpaces(bytes)); if (!v.has_value()) { return std::make_error_code(std::errc::invalid_argument); } value = v.value(); } else if constexpr (mcc::traits::mcc_output_char_range) { VT r; std::ranges::copy(bytes, std::back_inserter(r)); value = r; } else if constexpr (std::ranges::range) { using el_t = std::ranges::range_value_t; if constexpr (std::is_reference_v || std::is_const_v) { // no reference or constants allowed return std::make_error_code(std::errc::invalid_argument); } VT r; el_t elem; auto els = std::views::split(bytes, _rangeDelim); for (auto const& el : els) { ret = (*this)(std::string_view(el), elem); if (!ret) { std::back_inserter(r) = elem; } else { return std::make_error_code(std::errc::invalid_argument); } } value = r; } else if constexpr (mcc::traits::mcc_time_duration_c) { typename VT::rep vd; ret = (*this)(trimSpaces(bytes), vd); if (!ret) { value = VT{vd}; } } else { ret = std::make_error_code(std::errc::invalid_argument); } return ret; } protected: std::string _rangeDelim; }; /* key-value pair holder */ // to follow std::variant requirements (not references, not array, not void) template concept variant_valid_type_c = requires { !std::is_array_v && !std::is_void_v && !std::is_reference_v; }; template concept keyvalue_record_c = requires(T t) { requires std::same_as; requires variant_valid_type_c; }; template concept keyvalue_desc_c = requires(T t) { [](std::tuple) {}(t); }; template class KeyValueHolder { public: static constexpr std::string_view COMMENT_SEQ{"#"}; static constexpr std::string_view KEY_VALUE_DELIM{"="}; static constexpr std::string_view VALUE_ARRAY_DELIM{","}; inline static auto defaultDeserializeFunc = [](this auto&& self, std::string_view str, VT& value) { std::error_code ret{}; if constexpr (std::is_arithmetic_v) { auto v = mcc::utils::numFromStr(trimSpaces(str)); if (!v.has_value()) { return std::make_error_code(std::errc::invalid_argument); } value = v.value(); } else if constexpr (mcc::traits::mcc_output_char_range) { VT r; std::ranges::copy(str, std::back_inserter(r)); value = r; } else if constexpr (std::ranges::range) { using el_t = std::ranges::range_value_t; if constexpr (std::is_reference_v || std::is_const_v) { // no reference or constants allowed return std::make_error_code(std::errc::invalid_argument); } VT r; el_t elem; auto els = std::views::split(str, VALUE_ARRAY_DELIM); for (auto const& el : els) { ret = std::forward(self)(std::string_view(el), elem); if (!ret) { std::back_inserter(r) = elem; } else { return std::make_error_code(std::errc::invalid_argument); } } value = r; } else if constexpr (mcc::traits::mcc_time_duration_c) { typename VT::rep vd; ret = std::forward(self)(trimSpaces(str), vd); if (!ret) { value = VT{vd}; } } else { ret = std::make_error_code(std::errc::invalid_argument); } return ret; }; KeyValueHolder(DESCR_T desc) : _keyValue(desc) { [this](std::index_sequence) { ((_hashes[I] = FNV1aHash(std::get(_keyValue).key)), ...); }(std::make_index_sequence>()); } template std::expected getValue(std::string_view key) { T v; auto err = forKey(key, [&v](const VT& val) { if constexpr (std::convertible_to) { v = val; return std::error_code(); } else { return std::make_error_code(std::errc::invalid_argument); } }); if (err) { return std::unexpected(err); } else { return v; } } template std::error_code setValue(std::string_view key, const T& value) { return forKey(key, [value](VT& val) { if constexpr (std::convertible_to) { val = value; return std::error_code(); } else { return std::make_error_code(std::errc::invalid_argument); } }); } template std::error_code fromCharRange(const R& buffer, RecDelimT rec_delim = std::string_view("\n")) { // return fromCharRange(buffer, KeyValueHolder::defaultDeserializeFunc, std::move(rec_delim)); return fromCharRange(buffer, MccSimpleDeserializer{}.setRangeDelim(VALUE_ARRAY_DELIM), std::move(rec_delim)); } template std::error_code fromCharRange(const R& buffer, DeserFuncT&& deser_func, RecDelimT rec_delim = std::string_view("\n")) { // static_assert(mcc::traits::mcc_callable_c>, "!!!!!!!"); if constexpr (std::is_array_v>) { // char*, const char* if constexpr (std::is_array_v>) { return fromCharRange(std::string_view{buffer}, std::forward(deser_func), std::string_view(rec_delim)); } else { return fromCharRange(std::string_view{buffer}, std::forward(deser_func), std::move(rec_delim)); } } else { if constexpr (std::is_array_v>) { return fromCharRange(buffer, std::forward(deser_func), std::string_view(rec_delim)); } } std::error_code ec{}; std::string_view rec, key, value; auto recs = std::views::split(buffer, std::move(rec_delim)); for (auto const& el : recs) { rec = mcc::utils::trimSpaces(el, TrimType::TRIM_LEFT); if (rec.size()) { auto found = std::ranges::search(rec, COMMENT_SEQ); if (found.begin() != rec.end()) { // there was the comment sequence in record rec = std::string_view(rec.begin(), found.begin()); } if (rec.size()) { found = std::ranges::search(rec, KEY_VALUE_DELIM); if (found.begin() != rec.begin()) { // ignore an empty key key = trimSpaces(std::string_view(rec.begin(), found.begin()), TrimType::TRIM_RIGHT); value = std::string_view(found.end(), rec.end()); ec = forKey(key, [value, &deser_func](VT& v) { return deser_func(value, v); }); } } // just comment string starting from the beginning, just skip it (no error) } // empty record, just skip it (no error) if (ec) { break; } } return ec; } protected: DESCR_T _keyValue; std::array> _hashes; std::error_code forKey(std::string_view key, auto&& func) { return forHash<0>(FNV1aHash(key), std::forward(func)); } template std::error_code forHash(size_t hash, auto&& func) { if constexpr (I < std::tuple_size_v) { if (hash == _hashes[I]) { return std::forward(func)(std::get(_keyValue).value); } else { return forHash(hash, std::forward(func)); } } return std::make_error_code(std::errc::argument_out_of_domain); } }; } // namespace mcc::utils