From e483d06fe31a2569c58aed429fe6d69f4e7f208c Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Sun, 21 Jul 2019 22:02:19 +0200 Subject: Add compile time checked variant (needs C++20) --- cxxformat.hpp | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 262 insertions(+), 7 deletions(-) (limited to 'cxxformat.hpp') diff --git a/cxxformat.hpp b/cxxformat.hpp index b02fe4f..6a86e4c 100644 --- a/cxxformat.hpp +++ b/cxxformat.hpp @@ -52,7 +52,7 @@ template struct FormatConvert && !std::is_integral_v>> : ImplicitConversion {}; template -struct FormatConvert && !std::is_array_v> && !std::is_same_v, char>>> : ImplicitConversion {}; +struct FormatConvert && !std::is_array_v>>> : ImplicitConversion {}; template struct FormatConvert && std::is_array_v>>> : ImplicitConversion {}; @@ -60,7 +60,7 @@ struct FormatConvert struct AutoConversion { - static std::string conversion(const From &) + static bool conversion(const From &) { throw FormatException{""}; } @@ -69,9 +69,9 @@ struct AutoConversion template struct SimpleAutoConversion { - static std::string conversion(const From &) + static constexpr char conversion(const From &) { - return std::string{Conversion}; + return Conversion; } }; @@ -201,11 +201,29 @@ namespace detail template using string_or_string_view = typename StringOrStringViewHelper::type; - template - std::string autoConversionSpecifier(From &&from, std::size_t index = 0) + template + constexpr char autoConversionSpecifier(From &&from, std::size_t index = 0) { try { + if constexpr (!allowFallback) + { + if constexpr(!std::is_same_v>::conversion(std::forward(from)))>) + { + 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(std::forward(from)); } catch (const FormatException &) @@ -214,10 +232,11 @@ namespace detail { if constexpr (allowPointer) { - return "p"; + 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"}; } } @@ -378,4 +397,240 @@ std::string format(const String &fmt, Args &&... args) } } +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, '\0'}; + } + + template + static constexpr str prependCharHelper(const char c, const str &s, std::index_sequence) + { + return str{c, s.s[indices]..., '\0'}; + } + + template + constexpr str appendStrHelper(std::index_sequence, std::index_sequence) const + { + return str{s[ownIndices]..., other.s[othersIndices]..., '\0'}; + } +public: + + template + constexpr char get(str_index) const + { + static_assert(index < N, "str::get: index out of range"); + return s[index]; + } + + template + constexpr str extract(std::index_sequence, str_index = str_index<0>{}) const + { + static_assert(((indices + offset < N) && ...), "str::extract: index out of range"); + return str{s[indices + offset]..., '\0'}; + } + + 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; + +namespace detail +{ + template + constexpr char get(const str& s) + { + return s.get(str_index{}); + } + + template + constexpr str extractStr(const str &source, std::index_sequence) + { + return str{get(source)..., '\0'}; + } + + template + constexpr str substr(const str &s) + { + static_assert(!endIsIndex || end < N, "End index / substr-length is out of range"); + if constexpr (end == std::string::npos) return substr(s); + else if constexpr (!endIsIndex) return substr(s); + else if constexpr (endIsIndex && end + 1 - start == 0) return str{""}; + else return s.extract(std::make_index_sequence(), str_index{}); + } + + 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 std::pair getFlag() + { + constexpr char next = get(fmt); + if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return {next, i + 1}; + else return {0, i}; + } + + template + constexpr size_t getNumberEnd() + { + if constexpr (std::isdigit(get(fmt))) return getNumberEnd(); + else return i; + } + + template + constexpr char checkReplaceAutoConversion(Arg &&arg) + { + if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier(std::forward(arg), 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 auto flagData = getFlag(); + constexpr char flag = flagData.first; + constexpr char iAfterFlag = flagData.second; + 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>(std::forward(arg)); + + if constexpr (conversion == 'c') return substr<0, i - 1, true>(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 - 1, true>(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 - 1, true>(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 - 1, true>(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 - 1, true>(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 - 1, true>(fmt).s + strprintf((str{"%."}.s + std::to_string(determineStringPrecision(string)) + 's').c_str(), 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_s(Args &&...args) +{ + return detail::format_s(std::forward(args)...); +} } \ No newline at end of file -- cgit v1.2.3-54-g00ecf