diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2019-07-20 23:22:45 +0200 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2019-07-20 23:22:45 +0200 |
| commit | 14d7e7275eb54efdfe59ba3ce3b53634286c3d05 (patch) | |
| tree | c7d0eaed2826dda18a05da9e91d798c4a9a3418f /cxxformat.hpp | |
| download | cxxformat-14d7e7275eb54efdfe59ba3ce3b53634286c3d05.tar.gz cxxformat-14d7e7275eb54efdfe59ba3ce3b53634286c3d05.zip | |
Initial
Diffstat (limited to 'cxxformat.hpp')
| -rw-r--r-- | cxxformat.hpp | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/cxxformat.hpp b/cxxformat.hpp new file mode 100644 index 0000000..d4e8dfd --- /dev/null +++ b/cxxformat.hpp @@ -0,0 +1,435 @@ +/* +Copyright 2019 Mittendrein Markus + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#pragma once +#include <string> +#include <string_view> +#include <optional> +#include <cstring> +#include <stdexcept> +#include <cstdint> + +namespace format { + +using FormatException = std::invalid_argument; + +template<typename To, typename From, typename Enable = void> +struct FormatConvert {}; + +template<typename From> +struct FormatConvert<std::intmax_t, From, std::enable_if_t<std::is_signed_v<From> && std::is_integral_v<From>>> +{ + static constexpr std::intmax_t convert(From from) + { + return from; + } +}; + +template<typename From> +struct FormatConvert<std::intmax_t, From, std::enable_if_t<std::is_unsigned_v<From> && std::is_integral_v<From> && (sizeof(From) < sizeof(std::intmax_t))>> +{ + static constexpr std::intmax_t convert(From from) + { + return from; + } +}; + +template<typename From> +struct FormatConvert<std::uintmax_t, From, std::enable_if_t<std::is_unsigned_v<From> && std::is_integral_v<From>>> +{ + static constexpr std::uintmax_t convert(From from) + { + return from; + } +}; + +template<typename String> +struct FormatConvert<std::string_view, String, std::enable_if_t<std::is_convertible_v<String, std::string_view>>> +{ + static constexpr std::string_view convert(const String &str) + { + return str; + } +}; + +template<typename From> +struct FormatConvert<double, From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>> +{ + static constexpr double convert(const From& val) + { + return val; + } +}; + +template<typename Pointer> +struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && !std::is_array_v<std::remove_pointer_t<Pointer>> && !std::is_same_v<std::remove_pointer_t<Pointer>, char>>> +{ + static constexpr const void *convert(const Pointer* pointer) + { + return pointer; + } +}; + +template<typename Pointer> +struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && std::is_array_v<std::remove_pointer_t<Pointer>>>> +{ + static constexpr const void *convert(const Pointer pointer) + { + return pointer; + } +}; + +template<typename From, typename Enable = void> +struct AutoConversion +{ + static std::string conversion(const From &) + { + throw FormatException{""}; + } +}; + +template<typename From> +struct AutoConversion<From, std::enable_if_t<std::is_integral_v<From> && std::is_unsigned_v<From>>> +{ + static std::string conversion(const From &) + { + return "u"; + } +}; + +template<typename From> +struct AutoConversion<From, std::enable_if_t<std::is_integral_v<From> && std::is_signed_v<From>>> +{ + static std::string conversion(const From &) + { + return "d"; + } +}; + +template<typename From> +struct AutoConversion<From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>> +{ + static std::string conversion(const From &) + { + return "G"; + } +}; + +template<typename From> +struct AutoConversion<From, std::enable_if_t<std::is_convertible_v<From, std::string_view>>> +{ + static std::string conversion(const From &) + { + return "s"; + } +}; + +template<> +struct AutoConversion<char> +{ + static std::string conversion(const char) + { + return "c"; + } +}; + +namespace detail +{ + template<typename T> + struct remove_cvref { using type = std::remove_cv_t<std::remove_reference_t<T>>; }; + + template<typename T> + using remove_cvref_t = typename remove_cvref<T>::type; + + template<typename T, bool Ok = false> + struct StointHelper + { + static T convert(const std::string &, std::size_t *, int) + { + static_assert(Ok, "stoint: Unsupported type"); + return T{}; + } + }; + + template<typename T, T(converter)(const std::string &, std::size_t *, int)> + struct StointHelperConverter + { + static T convert(const std::string &str, std::size_t *pos, int base) + { + return converter(str, pos, base); + } + }; + + template<> + struct StointHelper<int> : StointHelperConverter<int, std::stoi> {}; + template<> + struct StointHelper<long> : StointHelperConverter<long, std::stol> {}; + template<> + struct StointHelper<long long> : StointHelperConverter<long long, std::stoll> {}; + template<> + struct StointHelper<unsigned long> : StointHelperConverter<unsigned long, std::stoul> {}; + template<> + struct StointHelper<unsigned long long> : StointHelperConverter<unsigned long long, std::stoull> {}; + + template<typename T> + T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10) + { + return StointHelper<T>::convert(str, pos, base); + } + + template<typename... Args> + std::string strprintf(const char *fmt, Args... args) + { + int size = std::snprintf(nullptr, 0, fmt, args...) + 1; + std::string result; + result.resize(size); + std::snprintf(result.data(), size, fmt, args...); + result.resize(size - 1); + return result; + } + + template<typename To, typename From, bool allowFallback, typename Enable = void> + struct FormatConvertHelper + { + static constexpr To convert(const From &) + { + static_assert(allowFallback, "Impossible conversion needed"); + throw FormatException{""}; + } + }; + + template<typename T, bool allowFallback> + struct FormatConvertHelper<T, T, allowFallback> + { + static constexpr const T &convert(const T &from) + { + return from; + } + }; + + template<typename To, typename From, bool allowFallback> + struct FormatConvertHelper<To, From, allowFallback, std::enable_if_t<!std::is_same_v<To, From> && std::is_same_v<remove_cvref_t<decltype(FormatConvert<To, remove_cvref_t<From>>::convert(std::declval<From>()))>, remove_cvref_t<To>>>> + { + static constexpr To convert(const From &from) + { + return FormatConvert<To, remove_cvref_t<From>>::convert(from); + } + }; + + template<typename To, bool allowFallback = true, typename From> + auto convert(const From &from, const char *toName, std::size_t index = 0) + { + try + { + return FormatConvertHelper<To, From, allowFallback>::convert(from); + } + catch (const FormatException &) + { + throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}}; + } + } + + template<typename From, typename Enable = void> + struct StringOrStringViewHelper + { + using type = std::string_view; + }; + + template<typename From> + struct StringOrStringViewHelper<From, std::enable_if_t<std::is_same_v<std::string, std::decay_t<decltype(FormatConvert<std::string, From>::convert(std::declval<From>()))>>>> + { + using type = std::string; + }; + + template<typename From> + using string_or_string_view = typename StringOrStringViewHelper<From>::type; + + template<bool allowPointer = false, typename From> + std::string autoConversionSpecifier(From &&from, std::size_t index = 0) + { + try + { + return AutoConversion<remove_cvref_t<From>>::conversion(std::forward<From>(from)); + } + catch (const FormatException &) + { + if constexpr (std::is_pointer_v<From>) + { + if constexpr (allowPointer) + { + return "p"; + } + else + { + throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"}; + } + } + throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion (%v / %V)"}; + } + } + + std::string format(std::string_view fmt, const std::size_t argumentIndex) + { + const auto fmtSize = fmt.size(); + for (std::size_t i = 0; i < fmtSize; ++i) + { + if (fmt[i] == '%') + { + if (i >= fmtSize - 1) + { + throw FormatException{"Incomplete directive"}; + } + + if (fmt[++i] == '%') + { + return std::string{fmt.substr(0, i)} + format(fmt.substr(i + 1), argumentIndex); + } + + throw FormatException{"Not enough arguments passed"}; + } + } + return std::string{fmt}; + } + + template<typename Arg, typename... Args> + std::string format(std::string_view fmt, const std::size_t argumentIndex, Arg &&arg, Args &&... args) + { + const auto fmtSize = fmt.size(); + for (std::size_t i = 0; i < fmtSize; ++i) + { + auto checkFmtLength = [i, fmtSize] + { + if (i >= fmtSize - 1) + { + throw FormatException{"Incomplete directive"}; + } + }; + const auto current = fmt[i]; + if (current == '%') + { + const auto directiveStart = i; + checkFmtLength(); + std::string specification{"%"}; + + const auto flag = fmt[++i]; + if (flag == '%') // handle %% here + { + return std::string{fmt.substr(0, directiveStart + 1)} + format(fmt.substr(i + 1), argumentIndex, std::forward<Arg>(arg), std::forward<Args>(args)...); + } + switch (flag) + { + case '#': case '0': case '-': case ' ': case '+': + specification.push_back(flag); + checkFmtLength(); + break; + default: + --i; + } + + std::string tempNumber; + // field width + while (std::isdigit(fmt[++i])) + { + tempNumber.push_back(fmt[i]); + checkFmtLength(); + } + --i; + const auto fieldWidth = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber); + tempNumber.clear(); + + std::optional<std::size_t> precision; + // precision + if (fmt[++i] == '.') + { + checkFmtLength(); + while (std::isdigit(fmt[++i])) + { + tempNumber.push_back(fmt[i]); + checkFmtLength(); + } + precision = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber); + } + --i; + + std::string conversion{fmt[++i]}; + if(conversion.front() == 'v') + { + conversion = autoConversionSpecifier(std::forward<Arg>(arg), argumentIndex); + } + else if(conversion.front() == 'V') + { + conversion = autoConversionSpecifier<true>(std::forward<Arg>(arg), argumentIndex); + } + + auto continueFormatting = [directiveStart, &args..., fmt, &specification, &fieldWidth, &precision, i, conversion, argumentIndex](auto length, auto converted) + { + if (fieldWidth) specification += std::to_string(fieldWidth); + if (precision) specification += '.' + std::to_string(*precision); + if (length) specification += length; + specification += conversion; + return std::string{fmt.substr(0, directiveStart)} + strprintf(specification.c_str(), converted) + format(fmt.substr(i + 1), argumentIndex + 1, std::forward<Args>(args)...); + }; + + switch (conversion.back()) + { + case 'c': + return continueFormatting(false, convert<char>(std::forward<Arg>(arg), "character", argumentIndex)); + + case 'd': case 'i': + return continueFormatting('j', convert<std::intmax_t>(std::forward<Arg>(arg), "signed integer", argumentIndex)); + + case 'u': case 'o': case 'x': case 'X': + return continueFormatting('j', convert<std::uintmax_t>(std::forward<Arg>(arg), "unsigned integer", argumentIndex)); + + case 's': + { + const auto& string = convert<string_or_string_view<remove_cvref_t<Arg>>>(std::forward<Arg>(arg), "string", argumentIndex); + precision = precision ? std::min(*precision, string.size()) : string.size(); + return continueFormatting(false, string.data()); + } + + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': + return continueFormatting(false, convert<double>(std::forward<Arg>(arg), "double", argumentIndex)); + + case 'p': + return continueFormatting(false, convert<const void *>(std::forward<Arg>(arg), "pointer", argumentIndex)); + + default: + throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""}; + } + } + } + + throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"}; + } +} + +template<bool NoThrow = false, typename String, typename... Args> +std::string format(const String &fmt, Args &&... args) +{ + const auto fmtStr = detail::convert<std::string_view, false>(fmt, "string"); + try + { + return detail::format(fmtStr, 1, std::forward<Args>(args)...); + } + catch (const FormatException &e) + { + const auto &message = std::string{"format(\""} + std::string{fmtStr} + "\", " + std::to_string(sizeof...(args)) + " more arguments...): " + e.what(); + + if constexpr (NoThrow) return message; + else throw FormatException(message); + } +} + +}
\ No newline at end of file |
