/* *****************************************************************************
* kblib is a general utility library for C++14 and C++17, intended to provide
* performant high-level abstractions and more expressive ways to do simple
* things.
*
* Copyright (c) 2021 killerbee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
* ****************************************************************************/
/**
* @file
* @brief Provides facilities to convert between various kinds of
* representations.
*
* @author killerbee
* @date 2019-2021
* @copyright GNU General Public Licence v3.0
*/
#if KBLIB_DEF_MACROS and not defined(pFromStr)
# define pFromStr(type, val) ::kblib::fromStr((val), # type)
#endif
#ifndef KBLIB_CONVERT_H
# define KBLIB_CONVERT_H
# include
# include
# include
# include
# include
# include
# include
# include
# include
# include
# include "algorithm.h"
# include "iterators.h"
# include "traits.h"
# if KBLIB_USE_STRING_VIEW
# include
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-W#warnings"
# include
# pragma GCC diagnostic pop
# include "stringops.h"
# endif
# include
namespace KBLIB_NS {
template
KBLIB_NODISCARD auto to_string(Int num) -> std::string {
static_assert(base <= 62 and base > 0, "Supported bases are 1 thru 62.");
constexpr auto digits = remove_null_terminator(
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
std::string ret;
bool neg = false;
if (num < 0) {
neg = true;
num *= -1;
} else if (num == 0) {
return "0";
}
do {
ret.push_back(digits[num % base]);
} while (num /= base);
if (neg) {
ret.push_back('-');
}
std::reverse(ret.begin(), ret.end());
return ret;
}
template
KBLIB_NODISCARD auto to_string(Int num, int base) -> std::string {
assert(base <= 62 and base > 0);
constexpr auto digits = remove_null_terminator(
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
std::string ret;
bool neg = false;
if (num < 0) {
neg = true;
num *= -1;
} else if (num == 0) {
return "0";
}
do {
ret.push_back(digits[num % base]);
} while (num /= base);
if (neg) {
ret.push_back('-');
}
std::reverse(ret.begin(), ret.end());
return ret;
}
/**
* @internal
*/
namespace detail_convert {
template
KBLIB_NODISCARD constexpr auto read_digits(const char* begin,
const char* end, unsigned base,
const char (&digits)[N])
-> Result {
if (begin == end) {
throw std::invalid_argument("\"\" is not an integer");
}
Result result{};
for (auto c : indirect(begin, end)) {
if (c != '\'') {
result *= base;
auto pos = find_in(std::begin(digits),
std::begin(digits) + base * variants, c);
if (pos != base * variants) {
result += pos / variants;
} else {
throw std::invalid_argument("invalid character in integer");
}
}
}
return result;
}
} // namespace detail_convert
template
KBLIB_NODISCARD constexpr auto parse_integer(const char* begin, const char* end,
int base = 0) -> Result {
if (begin == end) {
throw std::invalid_argument("\"\" is not an integer");
} else if (*begin == '-') {
return -parse_integer(begin + 1, end, base);
} else if (base == 0) {
if (*begin == '0') {
if (begin + 1 == end) {
return 0;
} else if (begin[1] == '-' or (begin + 2 != end and begin[2] == '-')) {
throw std::invalid_argument("unexpected - in integer");
} else {
switch (begin[1]) {
case 'x':
return parse_integer(begin + 2, end, 16);
case 'b':
return parse_integer(begin + 2, end, 2);
default:
return parse_integer(begin + 1, end, 8);
}
}
} else {
return parse_integer(begin, end, 10);
}
} else {
if (base < 2 or base > 62) {
throw std::invalid_argument(
"base must be either 0 or a positive number between 2 and 62");
} else if (base <= 36) {
return detail_convert::read_digits(
begin, end, to_unsigned(base),
"00112233445566778899AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRr"
"SsTtUuVvWwXxYyZz");
} else if (base <= 62) {
return detail_convert::read_digits(
begin, end, to_unsigned(base),
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
}
}
// silence warning that control may flow off the end even though all paths
// return or throw
return 0;
}
template
KBLIB_NODISCARD constexpr auto parse_integer(const char (&in)[N], int base = 0)
-> Result {
char t = in[N - 1];
return parse_integer(std::begin(in), std::end(in) - +(t == '\0'),
base);
}
template
KBLIB_NODISCARD constexpr auto parse_integer(const std::string& in,
int base = 0) -> Result {
return parse_integer(to_pointer(begin(in)), to_pointer(end(in)),
base);
}
# if KBLIB_USE_STRING_VIEW
template
KBLIB_NODISCARD constexpr auto parse_integer(std::string_view in, int base = 0)
-> Result {
return parse_integer(to_pointer(begin(in)), to_pointer(end(in)),
base);
}
# endif
template
struct constant : std::integral_constant {
constexpr auto operator-() -> constant { return {}; }
constexpr constant() = default;
constexpr /* implicit */ constant(std::integral_constant) noexcept {}
// reverse conversion handled by slicing
};
inline namespace literals {
template
KBLIB_NODISCARD constexpr auto operator""_c() {
constexpr char arr[] = {Cs...};
return constant(arr)>{};
}
template
KBLIB_NODISCARD constexpr auto operator""_cu() {
constexpr char arr[] = {Cs...};
return constant(arr)>{};
}
} // namespace literals
template ::value>::type>
KBLIB_NODISCARD constexpr auto etoi(E e) -> auto {
return static_cast>(e);
}
template
KBLIB_NODISCARD auto time_to_str(std::chrono::time_point& tp,
const std::string& fmt = "%F %T")
-> std::string {
std::time_t time = clock::to_time_t(tp);
std::tm* tmb = std::localtime(&time);
std::string ret{maxBufLen, '\0'};
std::strftime(&ret.front(), maxBufLen, fmt.c_str(), tmb);
return ret;
}
/**
* @namespace detail_units
* @internal
*/
namespace detail_units {
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::nanoseconds) noexcept
-> auto {
return "ns";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::microseconds) noexcept
-> auto {
return "us";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::milliseconds) noexcept
-> auto {
return "ms";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::seconds) noexcept
-> auto {
return "s";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::minutes) noexcept
-> auto {
return "min";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::hours) noexcept -> auto {
return "hr";
}
# if KBLIB_USE_CXX20
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::days) noexcept -> auto {
return "ns";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::weeks) noexcept -> auto {
return "ns";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::months) noexcept
-> auto {
return "ns";
}
KBLIB_NODISCARD constexpr auto unit_of(std::chrono::years) noexcept -> auto {
return "ns";
}
# endif
struct KBLIB_NODISCARD prefix {
char name[16];
char abbr[4];
};
// if std::intmax_t can represent the denominator
# if (-1U >> 63) > (1U << 18)
constexpr auto name_of(std::yocto) -> prefix { return prefix{"yocto", "y"}; }
# endif
# if (-1U >> 63) > (1U << 8)
constexpr auto name_of(std::zepto) -> prefix { return prefix{"zepto", "z"}; }
# endif
constexpr auto name_of(std::atto) -> prefix { return prefix{"atto", "a"}; }
constexpr auto name_of(std::femto) -> prefix { return prefix{"femto", "f"}; }
constexpr auto name_of(std::pico) -> prefix { return prefix{"pico", "p"}; }
constexpr auto name_of(std::nano) -> prefix { return prefix{"nano", "n"}; }
constexpr auto name_of(std::micro) -> prefix { return prefix{"micro", "u"}; }
constexpr auto name_of(std::milli) -> prefix { return prefix{"milli", "m"}; }
constexpr auto name_of(std::centi) -> prefix { return prefix{"centi", "c"}; }
constexpr auto name_of(std::deci) -> prefix { return prefix{"deci", "d"}; }
constexpr auto name_of(std::ratio<1, 1>) -> prefix { return prefix{"", ""}; }
constexpr auto name_of(std::deca) -> prefix { return prefix{"deca", "da"}; }
constexpr auto name_of(std::hecto) -> prefix { return prefix{"hecto", "h"}; }
constexpr auto name_of(std::kilo) -> prefix { return prefix{"kilo", "k"}; }
constexpr auto name_of(std::mega) -> prefix { return prefix{"mega", "M"}; }
constexpr auto name_of(std::giga) -> prefix { return prefix{"giga", "G"}; }
constexpr auto name_of(std::tera) -> prefix { return prefix{"tera", "T"}; }
constexpr auto name_of(std::peta) -> prefix { return prefix{"peta", "P"}; }
constexpr auto name_of(std::exa) -> prefix { return prefix{"exa", "E"}; }
// if std::intmax_t can represent the numerator
# if (-1U >> 63) > (1U << 8)
constexpr auto name_of(std::zetta) -> prefix { return prefix{"zetta", "Z"}; }
# endif
# if (-1U >> 63) > (1U << 18)
constexpr auto name_of(std::yotta) -> prefix { return prefix{"yotta", "Y"}; }
# endif
KBLIB_NODISCARD constexpr auto largest_power_1000(std::intmax_t in) -> int {
if (in % 1000 == 0) {
return 1 + largest_power_1000(in / 1000);
} else {
return 0;
}
}
KBLIB_NODISCARD constexpr auto largest_power_1000_p(double in) -> int {
if (in / 1000 >= 1) {
return 1 + largest_power_1000_p(in / 1000.);
} else {
return 0;
}
}
KBLIB_NODISCARD constexpr auto largest_power_1000(double in) -> int {
if (in < 1) {
return -largest_power_1000_p(1 / in);
}
if (in / 1000 >= 1) {
return 1 + largest_power_1000_p(in / 1000.);
} else {
return 0;
}
}
KBLIB_NODISCARD constexpr auto pow1000(int p) -> double {
auto r = 1.0;
if (p >= 0) {
while (p--) {
r *= 1000.;
}
} else {
while (p++) {
r /= 1000.;
}
}
return r;
}
template
struct is_si_ratio : std::false_type {};
// if std::intmax_t can represent the denominator
# if (-1U >> 63) > (1U << 18)
template <>
struct is_si_ratio : std::true_type {};
# endif
# if (-1U >> 63) > (1U << 8)
template <>
struct is_si_ratio : std::true_type {};
# endif
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio> : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
template <>
struct is_si_ratio : std::true_type {};
// if std::intmax_t can represent the numerator
# if (-1U >> 63) > (1U << 8)
template <>
struct is_si_ratio : std::true_type {};
# endif
# if (-1U >> 63) > (1U << 18)
template <>
struct is_si_ratio : std::true_type {};
# endif
template
struct KBLIB_NODISCARD unit_conversion {
const char* scale_prefix;
char abbr[6];
M multiplier;
};
template
auto ratio_to_SI() noexcept -> unit_conversion {
return {};
}
template
struct nearest_ratio {};
template
using nearest_ratio_t = typename nearest_ratio::type;
} // namespace detail_units
template <
typename Rep, typename Ratio,
enable_if_t::value>* = 0>
KBLIB_NODISCARD auto duration_to_str(std::chrono::duration& d)
-> std::string {
using ratio = typename Ratio::type;
auto cv = detail_units::ratio_to_SI();
return concat(d.count() * cv.multiplier, ' ', cv.abbr, 's');
}
template ::value>* = 0>
KBLIB_NODISCARD auto duration_to_str(std::chrono::duration& d)
-> std::string {
using ratio = typename Ratio::type;
using n_r = detail_units::nearest_ratio_t;
auto u = detail_units::name_of(n_r{});
// require an implicit cast
std::chrono::duration n_d = d;
return concat(n_d.count(), ' ', u.abbr, 's');
}
template
KBLIB_NODISCARD auto duration_to_str(
std::chrono::duration> d) -> std::string {
return concat(d.count(), " min");
}
template
KBLIB_NODISCARD auto duration_to_str(
std::chrono::duration> d) -> std::string {
return concat(d.count(), " hr");
}
template
KBLIB_NODISCARD auto url_encode(const string& value) -> std::string {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (char c : value) {
// Keep alphanumeric and other accepted characters intact
if (std::isalnum(c) or c == '-' or c == '_' or c == '.' or c == '~') {
escaped << c;
} else {
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int(to_unsigned(c));
escaped << std::nouppercase;
}
}
return escaped.str();
}
template
KBLIB_NODISCARD auto html_encode(const string& data) -> std::string {
std::string buffer;
// Arbitrary estimate for amount of growth caused by the escaping is 12.5%.
buffer.reserve(data.size() + data.size() / 8);
for (char c : data) {
switch (c) {
case '&':
buffer.append("&");
break;
case '\"':
buffer.append(""");
break;
case '\'':
buffer.append("'");
break;
case '<':
buffer.append("<");
break;
case '>':
buffer.append(">");
break;
default:
buffer.push_back(c);
break;
}
}
return buffer;
}
KBLIB_NODISCARD inline auto escapify(char c) -> std::string {
auto value = to_unsigned(c);
if (value < ' ' or value == '\x7F' or value & to_unsigned('\x80')) {
constexpr std::array digits{
remove_null_terminator("0123456789ABCDEF")};
std::string rc("\\x ");
rc[2] = digits[value >> 4u];
rc[3] = digits[value & 15u];
return rc;
} else {
return std::string(1, static_cast(value));
}
}
// Accepts any sequence of char, returns printable string
template
KBLIB_NODISCARD auto escapify(const string& value) -> std::string {
std::ostringstream ret;
for (char c : value) {
if (c < ' ' or c >= '\x7F') {
ret << escapify(c);
} else {
ret << c;
}
}
return ret.str();
}
// Given a string and a pointer into it, calculate the effective index of that
// pointer into a string such as created by kblib::escapify(value)
template
KBLIB_NODISCARD auto calculate_translated_index(string&& value, const char* p)
-> std::ptrdiff_t {
std::ptrdiff_t counter = 0;
for (auto&& c : value) {
if (&c == p) {
return counter;
}
counter += (std::isprint(c)) ? 1 : 4;
}
return counter;
}
KBLIB_NODISCARD inline auto calculate_translated_index(const char* value,
const char* p)
-> std::ptrdiff_t {
if (not value) {
throw std::invalid_argument(
"calculate_translated_index can't take a nullptr");
}
std::ptrdiff_t counter = 0;
while (*value) {
if (value == p) {
return counter;
}
counter += (std::isprint(*value)) ? 1 : 4;
}
return counter;
}
template
KBLIB_NODISCARD auto quoted(string&& in) -> std::string {
std::ostringstream ret;
ret << '"';
for (char c : in) {
if (c < ' ' or c >= '\x7F') {
ret << escapify(c);
} else if (c == '"') {
ret << "\\\"";
} else if (c == '\\') {
ret << "\\\\";
} else {
ret << c;
}
}
ret << '"';
return ret.str();
}
// This only uses RTTI because C++ has no other means to get "int" from a
// template parameter.
template
KBLIB_NODISCARD auto fromStr(const std::string& val,
const char* type = typeid(T).name()) -> T {
std::stringstream ss(val);
T ret{};
if (not (ss >> std::boolalpha >> ret).fail()) {
return ret;
} else {
throw std::runtime_error(kblib::quoted(val) + " is not a " + type);
}
}
template <>
KBLIB_NODISCARD inline auto fromStr(const std::string& val, const char*)
-> std::string {
return val;
}
template <>
KBLIB_NODISCARD inline auto fromStr(const std::string& val, const char* type)
-> bool {
if (val == "1" or val == "true") {
return true;
} else if (val == "0" or val == "false") {
return false;
} else {
throw std::runtime_error(kblib::quoted(val) + " is not a " + type);
}
}
template
KBLIB_NODISCARD auto fromStr(std::string&& val,
const char* type = typeid(T).name()) -> T {
std::stringstream ss(val);
T ret;
if (not (ss >> std::boolalpha >> ret).fail()) {
return ret;
} else {
throw std::runtime_error(kblib::quoted(val) + " is not a " + type);
}
}
template <>
KBLIB_NODISCARD inline auto fromStr(std::string&& val, const char*)
-> std::string {
return std::move(val);
}
template <>
KBLIB_NODISCARD inline auto fromStr(std::string&& val, const char* type)
-> bool {
if (val == "1" or val == "true") {
return true;
} else if (val == "0" or val == "false") {
return false;
} else {
throw std::runtime_error(kblib::quoted(val) + " is not a " + type);
}
}
# if KBLIB_USE_STRING_VIEW
template <>
KBLIB_NODISCARD inline auto fromStr(const std::string& val, const char*)
-> std::string_view {
return val;
}
template <>
inline auto fromStr(std::string&&, const char*) -> std::string_view = delete;
template
KBLIB_NODISCARD auto fromStr(std::string_view val,
const char* type = typeid(T).name()) -> T {
std::istrstream ss(val.data(), kblib::to_signed(val.size()));
T ret;
if (not (ss >> std::boolalpha >> ret).fail()) {
return ret;
} else {
throw std::runtime_error(kblib::quoted(val) + " is not a " + type);
}
}
template <>
KBLIB_NODISCARD inline auto fromStr(std::string_view val, const char*)
-> std::string_view {
return val;
}
template <>
KBLIB_NODISCARD inline auto fromStr(std::string_view val, const char*)
-> std::string {
return std::string(val);
}
template <>
KBLIB_NODISCARD inline auto fromStr(std::string_view val, const char* type)
-> bool {
if (val == "1" or val == "true") {
return true;
} else if (val == "0" or val == "false") {
return false;
} else {
throw std::runtime_error("\"" + std::string(val) + "\" is not a " + type);
}
}
template
KBLIB_NODISCARD auto fromStr(const char (&val)[N],
const char* type = typeid(To).name()) -> To {
// N - 1: remove null terminator
return fromStr(std::string_view(val, N - 1), type);
}
template
KBLIB_NODISCARD auto fromStr(const char* val,
const char* type = typeid(To).name(), _ = 0)
-> To {
return fromStr(std::string_view(val), type);
}
# endif
template
KBLIB_NODISCARD auto toStr(T val) -> std::string {
std::stringstream ss;
ss << val;
return ss.str();
}
KBLIB_NODISCARD inline auto toStr(std::string val) -> std::string {
return val;
}
template
struct lexical_caster {
static auto cast(const From& val, const char* type) -> To {
std::stringstream ss;
ss << val;
To ret;
if (not (ss >> ret).fail()) {
return ret;
} else {
throw std::runtime_error("Cannot convert \"" + toStr(val) + "\" to "
+ type);
}
}
};
template
struct lexical_caster {
static auto cast(const Same& val, const char*) -> Same { return val; }
};
template <>
struct lexical_caster {
static auto cast(const std::string& val, const char*) -> std::string {
return val;
}
};
template
struct lexical_caster {
static auto cast(const From& val, const char*) -> std::string {
return toStr(val);
}
};
template
struct lexical_caster {
static auto cast(const std::string& val, const char* type) -> To {
return fromStr(val, type);
}
};
# if KBLIB_USE_STRING_VIEW
template <>
struct lexical_caster {
static auto cast(const std::string_view& val, const char*)
-> std::string_view {
return val;
}
};
template <>
struct lexical_caster {
static auto cast(const std::string& val, const char*) -> std::string_view {
return val;
}
};
template
struct lexical_caster {
static std::enable_if_t,
std::string_view>
cast(const From& val, const char*) {
return From(val);
}
// DCL50-CPP-EX2:
// As stated in the normative text, C-style variadic functions that are
// declared but never defined are permitted.
auto cast(...) -> std::string_view = delete;
};
template
struct lexical_caster {
static auto cast(std::string_view val, const char* type) -> To {
return fromStr(val, type);
}
};
# endif
template
KBLIB_NODISCARD auto lexical_cast(const From& val,
const char* type = typeid(To).name()) -> To {
return lexical_caster::cast(val, type);
}
# if 0
template
KBLIB_NODISCARD auto lexical_cast(const From& val,
const char* type = typeid(To).name()) -> To {
using namespace std::literals;
if constexpr (std::is_same_v, std::decay_t>) {
return val;
} else if constexpr (std::is_same_v, std::string>) {
return toStr(val);
} else if constexpr (std::is_same_v, std::string>) {
return fromStr(val, type);
} else {
std::stringstream ss;
ss << val;
To ret;
if (not(ss >> ret).fail())
return ret;
else
throw std::runtime_error("Cannot convert \""s + toStr(val) + "\" to " +
type);
}
}
# endif
} // namespace KBLIB_NS
#endif // KBLIB_CONVERT_H