summaryrefslogtreecommitdiffstats
path: root/cxxformat.hpp
diff options
context:
space:
mode:
authorMarkus Mittendrein <git@maxmitti.tk>2019-07-21 22:02:19 +0200
committerMarkus Mittendrein <git@maxmitti.tk>2019-07-21 22:02:19 +0200
commite483d06fe31a2569c58aed429fe6d69f4e7f208c (patch)
treef8672f488e783fd196da3ed2cf366beb3927b2c1 /cxxformat.hpp
parent17f1b653c38a2f9478aaf9d6d762d0c8dd9e6f6f (diff)
downloadcxxformat-e483d06fe31a2569c58aed429fe6d69f4e7f208c.tar.gz
cxxformat-e483d06fe31a2569c58aed429fe6d69f4e7f208c.zip
Add compile time checked variant (needs C++20)
Diffstat (limited to 'cxxformat.hpp')
-rw-r--r--cxxformat.hpp269
1 files changed, 262 insertions, 7 deletions
diff --git a/cxxformat.hpp b/cxxformat.hpp
index b02fe4f..6a86e4c 100644
--- a/cxxformat.hpp
+++ b/cxxformat.hpp
@@ -52,7 +52,7 @@ template<typename From>
struct FormatConvert<double, From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>> : ImplicitConversion<double, const From &> {};
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>>> : ImplicitConversion<const void *, const Pointer *> {};
+struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && !std::is_array_v<std::remove_pointer_t<Pointer>>>> : ImplicitConversion<const void *, const 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>>>> : ImplicitConversion<const void*, const Pointer> {};
@@ -60,7 +60,7 @@ struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<P
template<typename From, typename Enable = void>
struct AutoConversion
{
- static std::string conversion(const From &)
+ static bool conversion(const From &)
{
throw FormatException{""};
}
@@ -69,9 +69,9 @@ struct AutoConversion
template<char Conversion, typename From>
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<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)
+ template<bool allowPointer = false, bool allowFallback = true, typename From>
+ constexpr char autoConversionSpecifier(From &&from, std::size_t index = 0)
{
try
{
+ if constexpr (!allowFallback)
+ {
+ if constexpr(!std::is_same_v<char, decltype(AutoConversion<remove_cvref_t<From>>::conversion(std::forward<From>(from)))>)
+ {
+ if constexpr (std::is_pointer_v<From>)
+ {
+ 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<remove_cvref_t<From>>::conversion(std::forward<From>(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<size_t i>
+struct str_index { static constexpr size_t n = i; };
+
+template<size_t N>
+struct str
+{
+public:
+ const char s[N + 1];
+ static constexpr size_t size = N;
+
+private:
+ template<size_t... indices>
+ constexpr str<N + 1> appendCharHelper(const char c, std::index_sequence<indices...>) const
+ {
+ return str<N + 1>{s[indices]..., c, '\0'};
+ }
+
+ template<size_t... indices>
+ static constexpr str<N + 1> prependCharHelper(const char c, const str<N> &s, std::index_sequence<indices...>)
+ {
+ return str<N + 1>{c, s.s[indices]..., '\0'};
+ }
+
+ template<auto other, size_t... ownIndices, size_t... othersIndices>
+ constexpr str<N + sizeof...(othersIndices)> appendStrHelper(std::index_sequence<ownIndices...>, std::index_sequence<othersIndices...>) const
+ {
+ return str<N + sizeof...(othersIndices)>{s[ownIndices]..., other.s[othersIndices]..., '\0'};
+ }
+public:
+
+ template<size_t index>
+ constexpr char get(str_index<index>) const
+ {
+ static_assert(index < N, "str::get: index out of range");
+ return s[index];
+ }
+
+ template<size_t offset, size_t... indices>
+ constexpr str<sizeof...(indices)> extract(std::index_sequence<indices...>, str_index<offset> = str_index<0>{}) const
+ {
+ static_assert(((indices + offset < N) && ...), "str::extract: index out of range");
+ return str<sizeof...(indices)>{s[indices + offset]..., '\0'};
+ }
+
+ constexpr str<N + 1> operator+(const char c) const
+ {
+ return appendCharHelper(c, std::make_index_sequence<N>());
+ }
+
+ template<size_t otherN>
+ constexpr str<N + otherN> operator+(const str<otherN> &other) const
+ {
+ return appendStrHelper<other>(std::make_index_sequence<N>(), std::make_index_sequence<otherN>());
+ }
+
+ template<size_t _N>
+ friend constexpr str<_N + 1> operator+(const char c, const str<_N> &other);
+};
+
+template<size_t N>
+constexpr str<N + 1> operator+(const char c, const str<N> &other)
+{
+ return str<N>::prependCharHelper(c, other, std::make_index_sequence<N>());
+}
+
+template<size_t N>
+str(const char(&)[N]) -> str<N - 1>;
+
+namespace detail
+{
+ template<size_t i, size_t N>
+ constexpr char get(const str<N>& s)
+ {
+ return s.get(str_index<i>{});
+ }
+
+ template<size_t N, size_t offset, size_t... indices>
+ constexpr str<sizeof...(indices)> extractStr(const str<N> &source, std::index_sequence<indices...>)
+ {
+ return str<sizeof...(indices)>{get<indices + offset>(source)..., '\0'};
+ }
+
+ template<size_t start, size_t end = std::string::npos, bool endIsIndex = false, size_t N>
+ constexpr str<end == std::string::npos ? N - start : (endIsIndex ? end + 1 - start : end)> substr(const str<N> &s)
+ {
+ static_assert(!endIsIndex || end < N, "End index / substr-length is out of range");
+ if constexpr (end == std::string::npos) return substr<start, N - 1, true>(s);
+ else if constexpr (!endIsIndex) return substr<start, start + end - 1, true>(s);
+ else if constexpr (endIsIndex && end + 1 - start == 0) return str{""};
+ else return s.extract(std::make_index_sequence<end - start + 1>(), str_index<start>{});
+ }
+
+ template<typename T, auto s, size_t i = 0, bool allowSign = true, T parsedPart = 0>
+ constexpr T strToInt()
+ {
+ if constexpr (i == s.size) return parsedPart;
+ else
+ {
+ constexpr char c = get<i>(s);
+ if constexpr (c == '-')
+ {
+ static_assert(allowSign, "strToInt: invalid character found");
+ static_assert(std::is_signed_v<T>, "strToInt: negative sign found when converting to an unsigned type");
+ return -strToInt<T, s, i + 1, false>();
+ }
+ else if constexpr (c == '+')
+ {
+ static_assert(allowSign, "strToInt: invalid character found");
+ return strToInt<T, s, i + 1, false>();
+ }
+ else
+ {
+ static_assert(c >= '0' && c <= '9', "strToInt: invalid character found");
+ return strToInt<T, s, i + 1, false, parsedPart * T{10} + T{c - '0'}>();
+ }
+ }
+ }
+
+ template<typename T, T N>
+ constexpr size_t decimalLength()
+ {
+ if constexpr (N < 0) return 1 + decimalLength<T, -N>();
+ else if constexpr (N < 10) return 1;
+ else return 1 + decimalLength<T, N / 10>();
+ }
+
+ template<typename T, T N>
+ constexpr str<decimalLength<T, N>()> toStr()
+ {
+ if constexpr (N < 0) return '-' + toStr<T, -N>();
+ else if constexpr (N < 10)
+ {
+ return str<1>{'0' + N};
+ }
+ else return toStr<T, N / 10>() + char(N % 10 + '0');
+ };
+
+ template<auto fmt, size_t i, size_t argumentIndex>
+ std::string format_s()
+ {
+ if constexpr (i == fmt.size) return "";
+ else if constexpr (get<i>(fmt) == '%')
+ {
+ if constexpr (get<i + 1>(fmt) == '%') return substr<0, i, true>(fmt).s + format_s<substr<i + 2>(fmt), 0, argumentIndex>();
+ else
+ {
+ static_assert(get<i + 1>(fmt) == '%', "Not enough arguments passed");
+ throw FormatException{"Not enough arguments passed"};
+ }
+ }
+ else return format_s<fmt, i + 1, argumentIndex>();
+ }
+
+ template<auto fmt, size_t i>
+ constexpr std::pair<char, size_t> getFlag()
+ {
+ constexpr char next = get<i>(fmt);
+ if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return {next, i + 1};
+ else return {0, i};
+ }
+
+ template<auto fmt, size_t i>
+ constexpr size_t getNumberEnd()
+ {
+ if constexpr (std::isdigit(get<i>(fmt))) return getNumberEnd<fmt, i + 1>();
+ else return i;
+ }
+
+ template<char conversion, size_t argumentIndex, typename Arg>
+ constexpr char checkReplaceAutoConversion(Arg &&arg)
+ {
+ if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier<conversion == 'V', false>(std::forward<Arg>(arg), argumentIndex);
+ else return conversion;
+ }
+
+ template<bool havePrecision, auto fmt, size_t iAfterFieldWidth, size_t iAfterPrecision, typename String>
+ size_t determineStringPrecision(const String& string)
+ {
+ if constexpr (havePrecision) return std::min(strToInt<size_t, substr<iAfterFieldWidth + 1, iAfterPrecision - 1, true>(fmt)>(), string.size());
+ else return string.size();
+ }
+
+ template<auto fmt, char conversion, size_t i, size_t iAfterPrecision, char length>
+ constexpr auto makeDirective()
+ {
+ if constexpr (length) return substr<i, iAfterPrecision - 1, true>(fmt) + length + conversion;
+ else return substr<i, iAfterPrecision - 1, true>(fmt) + conversion;
+ }
+
+ template<auto fmt, size_t i, size_t argumentIndex, typename Arg, typename... Args>
+ 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<i>(fmt) == '%')
+ {
+ if constexpr (get<i + 1>(fmt) == '%') return substr<0, i, true>(fmt).s + format_s<substr<i + 2>(fmt), 0, argumentIndex>(std::forward<Arg>(arg), std::forward<Args>(args)...);
+
+ constexpr auto flagData = getFlag<fmt, i + 1>();
+ constexpr char flag = flagData.first;
+ constexpr char iAfterFlag = flagData.second;
+ constexpr size_t iAfterFieldWidth = getNumberEnd<fmt, iAfterFlag>();
+
+ constexpr auto havePrecision = get<iAfterFieldWidth>(fmt) == '.';
+ constexpr size_t iAfterPrecision = [havePrecision, iAfterFieldWidth]() -> size_t { if constexpr (havePrecision) return getNumberEnd<fmt, iAfterFieldWidth + 1>(); else return iAfterFieldWidth; }();
+
+ constexpr char conversion = checkReplaceAutoConversion<get<iAfterPrecision>(fmt), argumentIndex>(std::forward<Arg>(arg));
+
+ if constexpr (conversion == 'c') return substr<0, i - 1, true>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 0>().s, convert<char, false>(std::forward<Arg>(arg), "character", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
+ else if constexpr (conversion == 'd' || conversion == 'i') return substr<0, i - 1, true>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 'j'>().s, convert<std::intmax_t, false>(std::forward<Arg>(arg), "signed integer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
+ else if constexpr (conversion == 'u' || conversion == 'o' || conversion == 'x' || conversion == 'X') return substr<0, i - 1, true>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 'j'>().s, convert<std::uintmax_t, false>(std::forward<Arg>(arg), "unsigned integer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(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<fmt, conversion, i, iAfterPrecision, 0>().s, convert<double, false>(std::forward<Arg>(arg), "double", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
+ else if constexpr (conversion == 'p') return substr<0, i - 1, true>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 0>().s, convert<const void *, false>(std::forward<Arg>(arg), "pointer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
+ else if constexpr (conversion == 's')
+ {
+ const auto& string = convert<string_or_string_view<remove_cvref_t<Arg>>, false>(std::forward<Arg>(arg), "string", argumentIndex);
+ return substr<0, i - 1, true>(fmt).s + strprintf((str{"%."}.s + std::to_string(determineStringPrecision<havePrecision, fmt, iAfterFieldWidth, iAfterPrecision>(string)) + 's').c_str(), string.data()) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
+ }
+ else
+ {
+ static_assert(fmt.size == 0, "Unknown conversion specifier!");
+ throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""};
+ }
+ }
+ else return format_s<fmt, i + 1, argumentIndex>(std::forward<Arg>(arg), std::forward<Args>(args)...);
+ }
+}
+
+template<auto fmt, typename... Args>
+std::string format_s(Args &&...args)
+{
+ return detail::format_s<fmt, 0, 1>(std::forward<Args>(args)...);
+}
} \ No newline at end of file