/* 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 #include #include #include #include #include namespace format { using FormatException = std::invalid_argument; template struct FormatConvert {}; template struct FormatConvert && std::is_integral_v>> { static constexpr std::intmax_t convert(From from) { return from; } }; template struct FormatConvert && std::is_integral_v && (sizeof(From) < sizeof(std::intmax_t))>> { static constexpr std::intmax_t convert(From from) { return from; } }; template struct FormatConvert && std::is_integral_v>> { static constexpr std::uintmax_t convert(From from) { return from; } }; template struct FormatConvert>> { static constexpr std::string_view convert(const String &str) { return str; } }; template struct FormatConvert && !std::is_integral_v>> { static constexpr double convert(const From& val) { return val; } }; template struct FormatConvert && !std::is_array_v> && !std::is_same_v, char>>> { static constexpr const void *convert(const Pointer* pointer) { return pointer; } }; template struct FormatConvert && std::is_array_v>>> { static constexpr const void *convert(const Pointer pointer) { return pointer; } }; template struct AutoConversion { static std::string conversion(const From &) { throw FormatException{""}; } }; template struct AutoConversion && std::is_unsigned_v>> { static std::string conversion(const From &) { return "u"; } }; template struct AutoConversion && std::is_signed_v>> { static std::string conversion(const From &) { return "d"; } }; template struct AutoConversion && !std::is_integral_v>> { static std::string conversion(const From &) { return "G"; } }; template struct AutoConversion>> { static std::string conversion(const From &) { return "s"; } }; template<> struct AutoConversion { static std::string conversion(const char) { return "c"; } }; namespace detail { template struct remove_cvref { using type = std::remove_cv_t>; }; template using remove_cvref_t = typename remove_cvref::type; template struct StointHelper { static T convert(const std::string &, std::size_t *, int) { static_assert(Ok, "stoint: Unsupported type"); return T{}; } }; template struct StointHelperConverter { static T convert(const std::string &str, std::size_t *pos, int base) { return converter(str, pos, base); } }; template<> struct StointHelper : StointHelperConverter {}; template<> struct StointHelper : StointHelperConverter {}; template<> struct StointHelper : StointHelperConverter {}; template<> struct StointHelper : StointHelperConverter {}; template<> struct StointHelper : StointHelperConverter {}; template T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10) { return StointHelper::convert(str, pos, base); } template 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 struct FormatConvertHelper { static constexpr To convert(const From &) { static_assert(allowFallback, "Impossible conversion needed"); throw FormatException{""}; } }; template struct FormatConvertHelper { static constexpr const T &convert(const T &from) { return from; } }; template struct FormatConvertHelper && std::is_same_v>::convert(std::declval()))>, remove_cvref_t>>> { static constexpr To convert(const From &from) { return FormatConvert>::convert(from); } }; template auto convert(const From &from, const char *toName, std::size_t index = 0) { try { return FormatConvertHelper::convert(from); } catch (const FormatException &) { throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}}; } } template struct StringOrStringViewHelper { using type = std::string_view; }; template struct StringOrStringViewHelper::convert(std::declval()))>>>> { using type = std::string; }; template using string_or_string_view = typename StringOrStringViewHelper::type; template std::string autoConversionSpecifier(From &&from, std::size_t index = 0) { try { return AutoConversion>::conversion(std::forward(from)); } catch (const FormatException &) { if constexpr (std::is_pointer_v) { 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 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), std::forward(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(tempNumber); tempNumber.clear(); std::optional precision; // precision if (fmt[++i] == '.') { checkFmtLength(); while (std::isdigit(fmt[++i])) { tempNumber.push_back(fmt[i]); checkFmtLength(); } precision = tempNumber.empty() ? 0 : stoint(tempNumber); } --i; std::string conversion{fmt[++i]}; if(conversion.front() == 'v') { conversion = autoConversionSpecifier(std::forward(arg), argumentIndex); } else if(conversion.front() == 'V') { conversion = autoConversionSpecifier(std::forward(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)...); }; switch (conversion.back()) { case 'c': return continueFormatting(false, convert(std::forward(arg), "character", argumentIndex)); case 'd': case 'i': return continueFormatting('j', convert(std::forward(arg), "signed integer", argumentIndex)); case 'u': case 'o': case 'x': case 'X': return continueFormatting('j', convert(std::forward(arg), "unsigned integer", argumentIndex)); case 's': { const auto& string = convert>>(std::forward(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(std::forward(arg), "double", argumentIndex)); case 'p': return continueFormatting(false, convert(std::forward(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 std::string format(const String &fmt, Args &&... args) { const auto fmtStr = detail::convert(fmt, "string"); try { return detail::format(fmtStr, 1, std::forward(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); } } }