From b65a703a2ea65a908655b873d1293560bfde9f56 Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Thu, 6 Jan 2022 03:47:39 +0100 Subject: New implementation for compile time format strings (run time format strings not yet) --- cxxformat.hpp | 656 ---------------------------------------------------------- 1 file changed, 656 deletions(-) delete mode 100644 cxxformat.hpp (limited to 'cxxformat.hpp') diff --git a/cxxformat.hpp b/cxxformat.hpp deleted file mode 100644 index 47c0cd7..0000000 --- a/cxxformat.hpp +++ /dev/null @@ -1,656 +0,0 @@ -/* -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 -#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 : ImplicitConversion {}; - -template -struct FormatConvert> : ImplicitConversion {}; - -template -struct FormatConvert : ImplicitConversion {}; - -template String> -struct FormatConvert : ImplicitConversion {}; - -template -struct FormatConvert : 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 : SimpleAutoConversion<'u', From> {}; - -template -struct AutoConversion : SimpleAutoConversion<'d', From> {}; - -template -struct AutoConversion : SimpleAutoConversion<'G', From> {}; - -template From> -struct AutoConversion : SimpleAutoConversion<'s', From> {}; - -template<> -struct AutoConversion : SimpleAutoConversion<'c', const char> {}; - -namespace detail -{ - 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()))>, std::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 -{ -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 fmt.s; - 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 - consteval void showUnknownConversionSpecifier() - { - static_assert(size == 0, "Unknown conversion specifier!"); - } - - 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 - { - showUnknownConversionSpecifier(); - 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)...); -} -} -- cgit v1.2.3-54-g00ecf