/* 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 ImplicitConversion { static constexpr To convert(From from) { return from; } }; template struct FormatConvert && std::is_integral_v>> : ImplicitConversion {}; template struct FormatConvert && std::is_integral_v && (sizeof(From) < sizeof(std::intmax_t))>> : ImplicitConversion {}; template struct FormatConvert && std::is_integral_v>> : ImplicitConversion {}; template struct FormatConvert>> : ImplicitConversion {}; template struct FormatConvert && !std::is_integral_v>> : ImplicitConversion {}; template struct FormatConvert && !std::is_array_v>>> : ImplicitConversion {}; template struct FormatConvert && std::is_array_v>>> : ImplicitConversion {}; template struct AutoConversion { static bool conversion() { throw FormatException{""}; } }; template struct SimpleAutoConversion { static constexpr char conversion() { return Conversion; } }; template struct AutoConversion && std::is_unsigned_v>> : SimpleAutoConversion<'u', From> {}; template struct AutoConversion && std::is_signed_v>> : SimpleAutoConversion<'d', From> {}; template struct AutoConversion && !std::is_integral_v>> : SimpleAutoConversion<'G', From> {}; template struct AutoConversion>> : SimpleAutoConversion<'s', From> {}; template<> struct AutoConversion : SimpleAutoConversion<'c', const char> {}; 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 constexpr char autoConversionSpecifier(std::size_t index = 0) { try { if constexpr (!allowFallback) { if constexpr(!std::is_same_v>::conversion())>) { if constexpr (std::is_pointer_v) { if constexpr (allowPointer) { return 'p'; } else { if constexpr (!allowFallback) static_assert(allowPointer, "%v doesn't allow automatic conversions for pointers; use %V to alsow allow arbitrary pointers"); else throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"}; } } } } return AutoConversion>::conversion(); } catch (const FormatException &) { if constexpr (std::is_pointer_v) { if constexpr (allowPointer) { return 'p'; } else { if constexpr (!allowFallback) static_assert(allowPointer, "%v doesn't allow automatic conversions for pointers; use %V to alsow allow arbitrary pointers"); 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{"%"}; if (fmt[i + 1] == '%') // handle %% here { return std::string{fmt.substr(0, directiveStart + 1)} + format(fmt.substr(i + 1), argumentIndex, std::forward(arg), std::forward(args)...); } while(i < fmtSize - 1) { switch (const auto flag = fmt[++i]) { case '#': case '0': case '-': case ' ': case '+': specification.push_back(flag); checkFmtLength(); break; default: --i; goto afterFlagLoop; } } afterFlagLoop: 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(argumentIndex); } else if(conversion.front() == 'V') { conversion = autoConversionSpecifier(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); } } template struct str_index { static constexpr size_t n = i; }; template struct str { public: const char s[N + 1]; static constexpr size_t size = N; private: template constexpr str appendCharHelper(const char c, std::index_sequence) const { return str{s[indices]..., c}; } template static constexpr str prependCharHelper(const char c, const str &s, std::index_sequence) { return str{c, s.s[indices]...}; } template constexpr str appendStrHelper(std::index_sequence, std::index_sequence) const { return str{s[ownIndices]..., other.s[othersIndices]...}; } public: template constexpr str(const char (&s)[N + 1], std::index_sequence) : s{s[indices]...} { } constexpr str(const char (&s)[N + 1]) : str{s, std::make_index_sequence()} { } template constexpr str(Chars... chars) : s{chars..., '\0'} { static_assert(N == sizeof...(chars), "O.o"); } template constexpr char get() const { static_assert(index < N, "str::get: index out of range"); return s[index]; } template constexpr str extract(std::index_sequence) const { static_assert(((indices + offset < N) && ...), "str::extract: index out of range"); return str{s[indices + offset]...}; } constexpr str operator+(const char c) const { return appendCharHelper(c, std::make_index_sequence()); } template constexpr str operator+(const str &other) const { return appendStrHelper(std::make_index_sequence(), std::make_index_sequence()); } template friend constexpr str<_N + 1> operator+(const char c, const str<_N> &other); }; template constexpr str operator+(const char c, const str &other) { return str::prependCharHelper(c, other, std::make_index_sequence()); } template str(const char(&)[N]) -> str; template str(Chars... chars) -> str; namespace detail { template constexpr char get(const str& s) { return s.template get(); } template constexpr str extractStr(const str &source, std::index_sequence) { return str{get(source)...}; } template constexpr str substr(const str &s) { static_assert(!endIsIndex || end < N, "substr: end index / substr-length is out of range"); if constexpr (!endIsIndex && end == 0) return str<0>{}; else if constexpr (end == std::string::npos) return substr(s); else if constexpr (!endIsIndex) return substr(s); else return s.template extract(std::make_index_sequence()); } template constexpr T strToInt() { if constexpr (i == s.size) return parsedPart; else { constexpr char c = get(s); if constexpr (c == '-') { static_assert(allowSign, "strToInt: invalid character found"); static_assert(std::is_signed_v, "strToInt: negative sign found when converting to an unsigned type"); return -strToInt(); } else if constexpr (c == '+') { static_assert(allowSign, "strToInt: invalid character found"); return strToInt(); } else { static_assert(c >= '0' && c <= '9', "strToInt: invalid character found"); return strToInt(); } } } template constexpr size_t decimalLength() { if constexpr (N < 0) return 1 + decimalLength(); else if constexpr (N < 10) return 1; else return 1 + decimalLength(); } template constexpr str()> toStr() { if constexpr (N < 0) return '-' + toStr(); else if constexpr (N < 10) { return str<1>{'0' + N}; } else return toStr() + char(N % 10 + '0'); } template std::string format_s() { if constexpr (i == fmt.size) return ""; else if constexpr (get(fmt) == '%') { if constexpr (get(fmt) == '%') return substr<0, i, true>(fmt).s + format_s(fmt), 0, argumentIndex>(); else { static_assert(get(fmt) == '%', "Not enough arguments passed"); throw FormatException{"Not enough arguments passed"}; } } else return format_s(); } template constexpr size_t getFlagEnd() { constexpr char next = get(fmt); if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return getFlagEnd(); else return i + offset; } template constexpr size_t getNumberEnd() { if constexpr (std::isdigit(get(fmt))) return getNumberEnd(); else return i; } template constexpr char checkReplaceAutoConversion() { if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier(argumentIndex); else return conversion; } template size_t determineStringPrecision(const String& string) { if constexpr (havePrecision) return std::min(strToInt(fmt)>(), string.size()); else return string.size(); } template constexpr auto makeDirective() { if constexpr (length) return substr(fmt) + length + conversion; else return substr(fmt) + conversion; } template std::string format_s(Arg &&arg, Args &&...args) { static_assert(i < fmt.size, "Too many arguments passed"); if constexpr (i == fmt.size) { throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"}; } else if constexpr (get(fmt) == '%') { if constexpr (get(fmt) == '%') return substr<0, i, true>(fmt).s + format_s(fmt), 0, argumentIndex>(std::forward(arg), std::forward(args)...); constexpr char iAfterFlag = getFlagEnd(); constexpr size_t iAfterFieldWidth = getNumberEnd(); constexpr auto havePrecision = get(fmt) == '.'; constexpr size_t iAfterPrecision = [havePrecision, iAfterFieldWidth]() -> size_t { if constexpr (havePrecision) return getNumberEnd(); else return iAfterFieldWidth; }(); constexpr char conversion = checkReplaceAutoConversion(fmt), argumentIndex, Arg>(); if constexpr (conversion == 'c') return substr<0, i>(fmt).s + strprintf(makeDirective().s, convert(std::forward(arg), "character", argumentIndex)) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); else if constexpr (conversion == 'd' || conversion == 'i') return substr<0, i>(fmt).s + strprintf(makeDirective().s, convert(std::forward(arg), "signed integer", argumentIndex)) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); else if constexpr (conversion == 'u' || conversion == 'o' || conversion == 'x' || conversion == 'X') return substr<0, i>(fmt).s + strprintf(makeDirective().s, convert(std::forward(arg), "unsigned integer", argumentIndex)) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); else if constexpr (conversion == 'e' || conversion == 'E' || conversion == 'f' || conversion == 'F' || conversion == 'g' || conversion == 'G' || conversion == 'a' || conversion == 'A') return substr<0, i>(fmt).s + strprintf(makeDirective().s, convert(std::forward(arg), "double", argumentIndex)) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); else if constexpr (conversion == 'p') return substr<0, i>(fmt).s + strprintf(makeDirective().s, convert(std::forward(arg), "pointer", argumentIndex)) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); else if constexpr (conversion == 's') { const auto& string = convert>, false>(std::forward(arg), "string", argumentIndex); return substr<0, i>(fmt).s + strprintf("%.*s", determineStringPrecision(string), string.data()) + format_s(fmt), 0, argumentIndex + 1>(std::forward(args)...); } else { static_assert(fmt.size == 0, "Unknown conversion specifier!"); throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""}; } } else return format_s(std::forward(arg), std::forward(args)...); } } template std::string format(Args &&...args) { return detail::format_s(std::forward(args)...); } }