summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarkus Mittendrein <maxmitti@maxmitti.tk>2022-01-06 03:47:39 +0100
committerMarkus Mittendrein <maxmitti@maxmitti.tk>2022-01-06 03:48:16 +0100
commitb65a703a2ea65a908655b873d1293560bfde9f56 (patch)
tree4e5751824ee3c1e533ef26964133ed9f471c47b6
parent7cbfe1904ce32e4d956d7a819ec6d851f51bc5ec (diff)
downloadcxxformat-b65a703a2ea65a908655b873d1293560bfde9f56.tar.gz
cxxformat-b65a703a2ea65a908655b873d1293560bfde9f56.zip
New implementation for compile time format strings (run time format strings not yet)
-rw-r--r--cxxformat.hpp656
-rw-r--r--cxxformat/core.hpp582
-rw-r--r--cxxformat/cxxformat.hpp7
-rw-r--r--cxxformat/file_ptr.hpp23
-rw-r--r--cxxformat/formatters.hpp480
-rw-r--r--cxxformat/helpers.hpp259
-rw-r--r--cxxformat/ostream.hpp27
-rw-r--r--cxxformat/string.hpp25
-rw-r--r--main.cpp144
9 files changed, 1501 insertions, 702 deletions
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 <string>
-#include <string_view>
-#include <optional>
-#include <cstring>
-#include <stdexcept>
-#include <cstdint>
-#include <concepts>
-
-namespace format {
-
-using FormatException = std::invalid_argument;
-
-template<typename To, typename From, typename Enable = void>
-struct FormatConvert {};
-
-template<typename To, typename From>
-struct ImplicitConversion
-{
- static constexpr To convert(From from)
- {
- return from;
- }
-};
-
-template<std::signed_integral From>
-struct FormatConvert<std::intmax_t, From> : ImplicitConversion<std::intmax_t, From> {};
-
-template<std::unsigned_integral From>
-struct FormatConvert<std::intmax_t, From, std::enable_if_t<(sizeof(From) < sizeof(std::intmax_t))>> : ImplicitConversion<std::intmax_t, From> {};
-
-template<std::unsigned_integral From>
-struct FormatConvert<std::uintmax_t, From> : ImplicitConversion<std::uintmax_t, From> {};
-
-template<std::convertible_to<std::string_view> String>
-struct FormatConvert<std::string_view, String> : ImplicitConversion<std::string_view, const String &> {};
-
-template<std::floating_point From>
-struct FormatConvert<long double, From> : ImplicitConversion<long 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>>>> : 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> {};
-
-template<typename From, typename Enable = void>
-struct AutoConversion
-{
- static bool conversion()
- {
- throw FormatException{""};
- }
-};
-
-template<char Conversion, typename From>
-struct SimpleAutoConversion
-{
- static constexpr char conversion()
- {
- return Conversion;
- }
-};
-
-template<std::unsigned_integral From>
-struct AutoConversion<From> : SimpleAutoConversion<'u', From> {};
-
-template<std::signed_integral From>
-struct AutoConversion<From> : SimpleAutoConversion<'d', From> {};
-
-template<std::floating_point From>
-struct AutoConversion<From> : SimpleAutoConversion<'G', From> {};
-
-template<std::convertible_to<std::string_view> From>
-struct AutoConversion<From> : SimpleAutoConversion<'s', From> {};
-
-template<>
-struct AutoConversion<char> : SimpleAutoConversion<'c', const char> {};
-
-namespace detail
-{
- template<typename T, bool Ok = false>
- struct StointHelper
- {
- static T convert(const std::string &, std::size_t *, int)
- {
- static_assert(Ok, "stoint: Unsupported type");
- return T{};
- }
- };
-
- template<typename T, T(converter)(const std::string &, std::size_t *, int)>
- struct StointHelperConverter
- {
- static T convert(const std::string &str, std::size_t *pos, int base)
- {
- return converter(str, pos, base);
- }
- };
-
- template<>
- struct StointHelper<int> : StointHelperConverter<int, std::stoi> {};
- template<>
- struct StointHelper<long> : StointHelperConverter<long, std::stol> {};
- template<>
- struct StointHelper<long long> : StointHelperConverter<long long, std::stoll> {};
- template<>
- struct StointHelper<unsigned long> : StointHelperConverter<unsigned long, std::stoul> {};
- template<>
- struct StointHelper<unsigned long long> : StointHelperConverter<unsigned long long, std::stoull> {};
-
- template<typename T>
- T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10)
- {
- return StointHelper<T>::convert(str, pos, base);
- }
-
- template<typename... Args>
- 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<typename To, typename From, bool allowFallback, typename Enable = void>
- struct FormatConvertHelper
- {
- static constexpr To convert(const From &)
- {
- static_assert(allowFallback, "Impossible conversion needed");
- throw FormatException{""};
- }
- };
-
- template<typename T, bool allowFallback>
- struct FormatConvertHelper<T, T, allowFallback>
- {
- static constexpr const T &convert(const T &from)
- {
- return from;
- }
- };
-
- template<typename To, typename From, bool allowFallback>
- struct FormatConvertHelper<To, From, allowFallback, std::enable_if_t<!std::is_same_v<To, From> && std::is_same_v<std::remove_cvref_t<decltype(FormatConvert<To, std::remove_cvref_t<From>>::convert(std::declval<From>()))>, std::remove_cvref_t<To>>>>
- {
- static constexpr To convert(const From &from)
- {
- return FormatConvert<To, std::remove_cvref_t<From>>::convert(from);
- }
- };
-
- template<typename To, bool allowFallback = true, typename From>
- auto convert(const From &from, const char *toName, std::size_t index = 0)
- {
- try
- {
- return FormatConvertHelper<To, From, allowFallback>::convert(from);
- }
- catch (const FormatException &)
- {
- throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}};
- }
- }
-
- template<typename From, typename Enable = void>
- struct StringOrStringViewHelper
- {
- using type = std::string_view;
- };
-
- template<typename From>
- struct StringOrStringViewHelper<From, std::enable_if_t<std::is_same_v<std::string, std::decay_t<decltype(FormatConvert<std::string, From>::convert(std::declval<From>()))>>>>
- {
- using type = std::string;
- };
-
- template<typename From>
- using string_or_string_view = typename StringOrStringViewHelper<From>::type;
-
- template<bool allowPointer = false, bool allowFallback = true, typename From>
- constexpr char autoConversionSpecifier(std::size_t index = 0)
- {
- try
- {
- if constexpr (!allowFallback)
- {
- if constexpr(!std::is_same_v<char, decltype(AutoConversion<std::remove_cvref_t<From>>::conversion())>)
- {
- 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<std::remove_cvref_t<From>>::conversion();
- }
- catch (const FormatException &)
- {
- 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");
- 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<typename Arg, typename... Args>
- 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>(arg), std::forward<Args>(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<std::size_t>(tempNumber);
- tempNumber.clear();
-
- std::optional<std::size_t> precision;
- // precision
- if (fmt[++i] == '.')
- {
- checkFmtLength();
- while (std::isdigit(fmt[++i]))
- {
- tempNumber.push_back(fmt[i]);
- checkFmtLength();
- }
- precision = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber);
- }
- --i;
-
- std::string conversion{fmt[++i]};
- if(conversion.front() == 'v')
- {
- conversion = autoConversionSpecifier<false, true, Arg>(argumentIndex);
- }
- else if(conversion.front() == 'V')
- {
- conversion = autoConversionSpecifier<true, true, 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>(args)...);
- };
-
- switch (conversion.back())
- {
- case 'c':
- return continueFormatting(false, convert<char>(std::forward<Arg>(arg), "character", argumentIndex));
-
- case 'd': case 'i':
- return continueFormatting('j', convert<std::intmax_t>(std::forward<Arg>(arg), "signed integer", argumentIndex));
-
- case 'u': case 'o': case 'x': case 'X':
- return continueFormatting('j', convert<std::uintmax_t>(std::forward<Arg>(arg), "unsigned integer", argumentIndex));
-
- case 's':
- {
- const auto& string = convert<string_or_string_view<std::remove_cvref_t<Arg>>>(std::forward<Arg>(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<double>(std::forward<Arg>(arg), "double", argumentIndex));
-
- case 'p':
- return continueFormatting(false, convert<const void *>(std::forward<Arg>(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<bool NoThrow = false, typename String, typename... Args>
-std::string format(const String &fmt, Args &&... args)
-{
- const auto fmtStr = detail::convert<std::string_view, false>(fmt, "string");
- try
- {
- return detail::format(fmtStr, 1, std::forward<Args>(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<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};
- }
-
- 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]...};
- }
-
- 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]...};
- }
-
-public:
- template<size_t... indices>
- constexpr str(const char (&s)[N + 1], std::index_sequence<indices...>) : s{s[indices]...}
- {
-
- }
-
- constexpr str(const char (&s)[N + 1]) : str{s, std::make_index_sequence<N + 1>()}
- {
-
- }
-
- template<typename... Chars>
- constexpr str(Chars... chars) : s{chars..., '\0'}
- {
- static_assert(N == sizeof...(chars), "O.o");
- }
-
- template<size_t index>
- constexpr char get() 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...>) const
- {
- static_assert(((indices + offset < N) && ...), "str::extract: index out of range");
- return str<sizeof...(indices)>{s[indices + offset]...};
- }
-
- 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>;
-
-template<typename... Chars>
-str(Chars... chars) -> str<sizeof...(Chars)>;
-
-namespace detail
-{
- template<size_t i, size_t N>
- constexpr char get(const str<N>& s)
- {
- return s.template get<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)...};
- }
-
- 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, "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<start, N - 1, true>(s);
- else if constexpr (!endIsIndex) return substr<start, start + end - 1, true>(s);
- else return s.template extract<start>(std::make_index_sequence<end - start + 1>());
- }
-
- 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 fmt.s;
- 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, size_t offset = 0>
- constexpr size_t getFlagEnd()
- {
- constexpr char next = get<i + offset>(fmt);
- if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return getFlagEnd<fmt, i, offset + 1>();
- else return i + offset;
- }
-
- 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()
- {
- if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier<conversion == 'V', false, 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<char c, std::size_t size>
- consteval void showUnknownConversionSpecifier()
- {
- static_assert(size == 0, "Unknown conversion specifier!");
- }
-
- 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 char iAfterFlag = getFlagEnd<fmt, i + 1>();
- 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, Arg>();
-
- if constexpr (conversion == 'c') return substr<0, i>(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>(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>(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>(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>(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<std::remove_cvref_t<Arg>>, false>(std::forward<Arg>(arg), "string", argumentIndex);
- return substr<0, i>(fmt).s + strprintf("%.*s", determineStringPrecision<havePrecision, fmt, iAfterFieldWidth, iAfterPrecision>(string), string.data()) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...);
- }
- else
- {
- showUnknownConversionSpecifier<conversion, fmt.size>();
- 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<str fmt, typename... Args>
-std::string format(Args &&...args)
-{
- return detail::format_s<fmt, 0, 1>(std::forward<Args>(args)...);
-}
-}
diff --git a/cxxformat/core.hpp b/cxxformat/core.hpp
new file mode 100644
index 0000000..3b665a5
--- /dev/null
+++ b/cxxformat/core.hpp
@@ -0,0 +1,582 @@
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cassert>
+#include <charconv>
+#include <cmath>
+#include <cstddef>
+#include <limits>
+#include <optional>
+#include <stdexcept>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include "helpers.hpp"
+
+namespace format {
+ namespace {
+ template<std::size_t N>
+ struct str
+ {
+ public:
+ const char s[N + 1];
+
+ constexpr std::size_t size() const noexcept { return N; }
+
+ constexpr str(const char (&s)[N + 1]) noexcept : str{s, std::make_index_sequence<N + 1>()} {}
+ constexpr str(std::string_view s) noexcept : str{s, std::make_index_sequence<N>()} {}
+
+ constexpr operator std::string_view() const noexcept
+ {
+ return {s, N};
+ }
+
+ constexpr bool operator==(std::string_view other) const noexcept
+ {
+ return operator std::string_view() == other;
+ }
+
+ template<std::size_t pos, std::size_t count>
+ consteval auto substr(holder<pos>, holder<count>) const noexcept
+ {
+ static_assert(pos + count <= N);
+ return str<count>{s, pos, std::make_index_sequence<count>()};
+ }
+
+ template<std::size_t begin, std::size_t end>
+ consteval auto substr(holder<begin, end>) const noexcept
+ {
+ return substr(holder<begin>{}, holder<end - begin>{});
+ }
+
+ template<std::size_t pos>
+ consteval auto substr(holder<pos> p) const noexcept
+ {
+ return substr(p, holder<N - pos>{});
+ }
+
+ constexpr char operator[](std::size_t i) const noexcept
+ {
+ return s[i];
+ }
+
+ private:
+ template<std::size_t... indices>
+ constexpr str(const char (&s)[N + 1], std::index_sequence<indices...>) noexcept : s{s[indices]...} {}
+
+ template<std::size_t M, std::size_t... indices>
+ constexpr str(const char (&s)[M], std::size_t offset, std::index_sequence<indices...>) noexcept : s{s[indices + offset]..., '\0'} {}
+
+ template<std::size_t... indices>
+ constexpr str(std::string_view s, std::index_sequence<indices...>) noexcept : s{s[indices]..., '\0'} {}
+
+ friend std::ostream& operator<<(std::ostream& out, const str& s)
+ {
+ return out << s.operator std::string_view();
+ }
+
+ template<std::size_t M>
+ friend struct str;
+ };
+
+ template<std::size_t N>
+ str(const char(&)[N]) -> str<N - 1>;
+
+ template<typename FormatOutput>
+ struct FormatOutputBase {
+ constexpr void operator()(std::string_view what) const
+ {
+ this_().output(what);
+ }
+
+ constexpr void operator()(std::string_view what, std::size_t count) const
+ {
+ for (std::size_t i = 0; i < count; ++i)
+ {
+ this_()(what);
+ }
+ }
+
+ constexpr void operator()(char what) const
+ {
+ this_()({&what, 1});
+ }
+
+ constexpr void operator()(char what, std::size_t count) const
+ {
+ this_()({&what, 1}, count);
+ }
+
+ private:
+ constexpr const FormatOutput& this_() const
+ {
+ static_assert(std::derived_from<FormatOutput, FormatOutputBase<FormatOutput>>, "FormatOutputBase must be used with CRTP");
+ static_assert(requires(const FormatOutput o, std::string_view s) { o.output(s); }, "The derived class must implement the method output(std::string_view)");
+ return static_cast<const FormatOutput&>(*this);
+ }
+ };
+
+ template<typename T>
+ concept format_output = std::derived_from<std::remove_cvref_t<T>, FormatOutputBase<std::remove_cvref_t<T>>> && requires(const T o, std::string_view s) { o.output(s); };
+
+ struct NullOutput : FormatOutputBase<NullOutput> {
+ constexpr void output(std::string_view) const noexcept {}
+ };
+
+ using ArgIndex = std::uint16_t;
+ struct format_specifier {
+ enum Length : std::uint8_t {
+ None,
+ Quarter, // hh
+ Half, // h
+ Long, // l
+ LongLong, // ll
+ LongDouble, // L
+ Maximum, // j
+ SizeT, // z
+ PtrDiff // t
+ };
+
+ optional_int<ArgIndex> minWidth;
+ optional_int<ArgIndex> precision;
+ ArgIndex argIndex;
+ char conversion;
+ char addSign;
+ char padding;
+ struct {
+ bool leftJustified : 1;
+ bool alternative : 1;
+ bool widthAsArg : 1;
+ bool precisionAsArg : 1;
+ Length length : 4;
+ };
+ };
+ }
+
+ template<format_output Out>
+ constexpr decltype(auto) make_output(Out&& out) { return std::forward<Out>(out); }
+
+ template<typename T>
+ concept format_output_compatible = format_output<T> || requires(T&& o) { {make_output(std::forward<T>(o))} -> format_output; };
+
+ template<typename T, bool fallback = false>
+ struct formatter;
+
+ template<typename T>
+ concept has_formatter = requires(T t, format_specifier spec, const NullOutput& out, optional_int<std::size_t> z) {
+ { formatter<T>::format(out, t, spec, z, z) } -> std::same_as<void>;
+ { formatter<T>::template format<format_specifier{}>(out, t, z, z) } -> std::same_as<void>;
+ };
+
+ template<typename T>
+ concept has_fallback_formatter = requires(T t, format_specifier spec, const NullOutput& out, optional_int<std::size_t> z) {
+ { formatter<T, true>::format(out, t, spec, z, z) } -> std::same_as<void>;
+ { formatter<T, true>::template format<format_specifier{}>(out, t, z, z) } -> std::same_as<void>;
+ };
+
+ template<typename T>
+ concept has_some_formatter = has_formatter<T> || has_fallback_formatter<T>;
+
+ namespace {
+ template<typename T>
+ struct formatter_with_fallback : formatter<T> {};
+
+ template<has_fallback_formatter T> requires (!has_formatter<T>)
+ struct formatter_with_fallback<T> : formatter<T, true> {};
+
+ template<std::integral T>
+ constexpr std::optional<T> digit(char c) noexcept
+ {
+ if (c >= '0' && c <= '9')
+ {
+ return c - '0';
+ }
+ return std::nullopt;
+ }
+
+ template<std::integral T = int>
+ constexpr std::pair<std::optional<T>, std::size_t> parseNumber(std::string_view s, std::size_t pos) noexcept
+ {
+ if (pos >= s.size())
+ {
+ return {std::nullopt, pos};
+ }
+
+ bool isNegative = (std::signed_integral<T> && s[pos] == '-');
+ auto startPos = isNegative ? pos + 1 : pos;
+ if (startPos >= s.size())
+ {
+ return {std::nullopt, pos};
+ }
+
+ auto d = digit<T>(s[startPos]);
+ if (!d)
+ {
+ return {std::nullopt, pos};
+ }
+
+ T val{*d};
+ auto p = startPos + 1;
+ for (; p < s.size(); ++p)
+ {
+ auto nextD = digit<T>(s[p]);
+ if (!nextD)
+ {
+ break;
+ }
+
+ val *= 10;
+ val += *nextD;
+ }
+ return {isNegative ? -val : val, p};
+ }
+
+ constexpr auto parseSpec(std::string_view fmt, std::size_t pos, ArgIndex nextArg)
+ {
+ struct Result {
+ std::size_t pos;
+ format_specifier spec;
+ ArgIndex nextArg;
+ ArgIndex maxArg;
+ };
+
+ const auto checkEos = [&pos, fmt](std::optional<std::size_t> p = std::nullopt) {
+ if (p.value_or(pos) >= fmt.size())
+ {
+ throw std::invalid_argument{"Incomplete format specifier at end of format string"};
+ }
+ };
+
+ format_specifier spec{
+ .minWidth = std::nullopt,
+ .precision = std::nullopt,
+ .addSign = '\0',
+ .padding = ' ',
+ };
+ spec.leftJustified = false;
+ spec.alternative = false;
+ spec.widthAsArg = false;
+ spec.precisionAsArg = false;
+ spec.length = format_specifier::None;
+
+ bool hasExplicitArgIndex = false;
+ const auto argIndex = parseNumber<ArgIndex>(fmt, pos);
+ if (argIndex.first)
+ {
+ checkEos(argIndex.second);
+ if (fmt[argIndex.second] == '$')
+ {
+ if (*argIndex.first == 0)
+ {
+ throw std::invalid_argument{"Placeholder indices are 1-based and can’t be 0"};
+ }
+ spec.argIndex = *argIndex.first - 1;
+ hasExplicitArgIndex = true;
+ pos = argIndex.second + 1;
+ checkEos();
+ }
+ }
+
+ for (; pos < fmt.size(); ++pos)
+ {
+ const auto c = fmt[pos];
+ if (c == '-')
+ {
+ spec.leftJustified = true;
+ }
+ else if (c == '+')
+ {
+ spec.addSign = '+';
+ }
+ else if (c == ' ')
+ {
+ spec.addSign = ' ';
+ }
+ else if (c == '#')
+ {
+ spec.alternative = true;
+ }
+ else if (c == '0')
+ {
+ spec.padding = '0';
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (spec.leftJustified)
+ {
+ spec.padding = ' ';
+ }
+ checkEos();
+
+ ArgIndex maxArg = 0;
+ const auto argIndexOrNextArg = [&pos, &nextArg, &maxArg, checkEos, fmt](decltype(*format_specifier::minWidth)& argIndex) {
+ const auto index = parseNumber<ArgIndex>(fmt, pos);
+ checkEos(index.second);
+ if (index.first && fmt[index.second] == '$')
+ {
+ if (*index.first == 0)
+ {
+ throw std::invalid_argument{"Placeholder indices are 1-based and can’t be 0"};
+ }
+ argIndex = *index.first - 1;
+ pos = index.second + 1;
+ checkEos();
+ }
+ else
+ {
+ argIndex = nextArg;
+ ++nextArg;
+ }
+ maxArg = std::max(maxArg, argIndex);
+ };
+
+ const auto handleWidthPrecision = [&pos, argIndexOrNextArg, fmt, checkEos](decltype(format_specifier::minWidth)& indexOrValue, bool allowEmpty = false){
+ bool asArg = false;
+ if (fmt[pos] == '*')
+ {
+ asArg = true;
+ ++pos;
+ checkEos();
+ argIndexOrNextArg(indexOrValue.emplace());
+ }
+ else
+ {
+ const auto width = parseNumber<ArgIndex>(fmt, pos);
+ if (width.first)
+ {
+ indexOrValue = *width.first;
+ pos = width.second;
+ }
+ else if (allowEmpty)
+ {
+ indexOrValue = 0;
+ }
+ }
+ return asArg;
+ };
+ spec.widthAsArg = handleWidthPrecision(spec.minWidth);
+
+
+ if (fmt[pos] == '.')
+ {
+ ++pos;
+ checkEos();
+ spec.precisionAsArg = handleWidthPrecision(spec.precision, true);
+ }
+
+ {
+ using L = format_specifier::Length;
+ const auto c = fmt[pos];
+ if (c == 'h' || c == 'l')
+ {
+ if (fmt[pos + 1] == c)
+ {
+ ++pos;
+ checkEos();
+ spec.length = (c == 'h' ? L::Quarter : L::LongLong);
+ ++pos;
+ }
+ else
+ {
+ spec.length = (c == 'h' ? L::Half : L::Long);
+ ++pos;
+ }
+ }
+ else if (c == 'L')
+ {
+ spec.length = L::LongDouble;
+ ++pos;
+ }
+ else if (c == 'j')
+ {
+ spec.length = L::Maximum;
+ ++pos;
+ }
+ else if (c == 'z')
+ {
+ spec.length = L::SizeT;
+ ++pos;
+ }
+ else if (c == 't')
+ {
+ spec.length = L::PtrDiff;
+ ++pos;
+ }
+ }
+ checkEos();
+
+ spec.conversion = fmt[pos];
+ ++pos;
+
+ if (!hasExplicitArgIndex)
+ {
+ spec.argIndex = nextArg;
+ ++nextArg;
+ }
+ maxArg = std::max(maxArg, spec.argIndex);
+
+ return Result{pos, spec, nextArg, static_cast<ArgIndex>(maxArg + 1)};
+ }
+
+ constexpr bool checkValidConversion(char c) noexcept
+ {
+ return std::string_view{"diouxXeEfFgGaAcspv"}.find(c) != std::string_view::npos;
+ }
+
+ namespace compile_time {
+ template<typename T>
+ struct is_str_s : std::false_type {};
+
+ template<std::size_t N>
+ struct is_str_s<str<N>> : std::true_type {};
+
+ template<typename T>
+ concept is_str = is_str_s<T>::value;
+
+ template<typename T>
+ concept template_part = std::same_as<T, format_specifier> || is_str<T>;
+
+ template<ArgIndex argIndex, bool used>
+ consteval void assertArgumentUsed()
+ {
+ static_assert(used, "Argument is not used (1-based argIndex)");
+ }
+
+ template<ArgIndex argCount, template_part auto... parts>
+ struct format_template {
+ template<format_output_compatible Output, typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...)
+ constexpr void operator()(Output&& output, Args&&... args) const
+ {
+ checkArgCount<sizeof...(Args)>();
+
+ const auto out = make_output(std::forward<Output>(output));
+ const auto format_arg = [&args..., &out]<format_specifier spec>(holder<spec>) -> decltype(auto)
+ {
+ optional_int<std::size_t> minWidth = spec.minWidth;
+ if constexpr (spec.widthAsArg)
+ {
+ using Arg = std::decay_t<nth_type<*spec.minWidth, Args...>>;
+ static_assert(std::unsigned_integral<Arg>, "Width argument must be an unsigned integral");
+ static_assert(sizeof(Arg) <= sizeof(std::size_t), "Width argument type must be at most as big as std::size_t");
+ minWidth = nth_argument<*spec.minWidth>(std::forward<Args>(args)...);
+ }
+ optional_int<std::size_t> precision = spec.precision;
+ if constexpr (spec.precisionAsArg)
+ {
+ using Arg = std::decay_t<nth_type<*spec.precision, Args...>>;
+ static_assert(std::unsigned_integral<Arg>, "Precision argument must be an unsigned integral");
+ static_assert(sizeof(Arg) <= sizeof(std::size_t), "Precision argument type must be at most as big as std::size_t");
+ precision = nth_argument<*spec.precision>(std::forward<Args>(args)...);
+ }
+ auto&& arg = nth_argument<spec.argIndex>(std::forward<Args>(args)...);
+ return formatter_with_fallback<std::decay_t<decltype(arg)>>::template format<spec>(out, arg, minWidth, precision);
+ };
+
+ (([&]{
+ if constexpr (is_str<std::decay_t<decltype(parts)>>)
+ {
+ out(parts);
+ }
+ else
+ {
+ format_arg(holder<parts>{});
+ }
+ })(), ...);
+ }
+
+ private:
+ template<std::size_t i>
+ consteval static void checkArgCount()
+ {
+ if constexpr (i != argCount)
+ {
+ static_assert(i > argCount, "Not enough arguments passed.");
+ static_assert(i < argCount, "Too many arguments passed.");
+ }
+ }
+
+ consteval static void checkArgsUsed()
+ {
+ checkArgsUsed(std::make_index_sequence<argCount>());
+ }
+
+ template<std::size_t... indices>
+ consteval static void checkArgsUsed(std::index_sequence<indices...>)
+ {
+ (checkArgUsed<indices>(), ...);
+ }
+
+ template<std::size_t i>
+ consteval static void checkArgUsed()
+ {
+ assertArgumentUsed<i + 1, ([]{
+ if constexpr (is_str<std::decay_t<decltype(parts)>>)
+ {
+ return false;
+ }
+ else
+ {
+ return parts.argIndex == i || (parts.widthAsArg && parts.minWidth == i) || (parts.precisionAsArg && parts.precision == i);
+ }
+ }() || ...)>();
+ }
+
+ static_assert((checkArgsUsed(), true));
+ };
+
+ template<str fmt, std::size_t pos, ArgIndex nextArg>
+ consteval auto parseSpec() noexcept
+ {
+ constexpr auto specResult = parseSpec(fmt, pos, nextArg);
+ static_assert(checkValidConversion(specResult.spec.conversion), "Invalid conversion specifier");
+ return specResult;
+ }
+
+ template<str fmt, std::size_t pos, ArgIndex nextArg, ArgIndex maxArg, template_part auto... parts>
+ consteval auto parseFormat() noexcept
+ {
+ if constexpr (pos >= fmt.size())
+ {
+ return format_template<maxArg, parts...>{};
+ }
+ else
+ {
+ constexpr auto nextSpec = std::string_view{fmt}.find('%', pos);
+ if constexpr (nextSpec == std::string_view::npos)
+ {
+ return parseFormat<fmt, nextSpec, nextArg, maxArg, parts..., fmt.substr(holder<pos>{})>();
+ }
+ else
+ {
+ static_assert(nextSpec + 1 < fmt.size(), "Incomplete format specifier at end of format string");
+
+ constexpr auto specStart = fmt[nextSpec + 1];
+ if constexpr (specStart == '%')
+ {
+ return parseFormat<fmt, nextSpec + 2, nextArg, maxArg, parts..., fmt.substr(holder<pos, nextSpec>{}), str{"%"}>();
+ }
+ else
+ {
+ constexpr auto result = parseSpec<fmt, nextSpec + 1, nextArg>();
+ return parseFormat<fmt, result.pos, result.nextArg, std::max(maxArg, result.maxArg), parts..., fmt.substr(holder<pos, nextSpec>{}), result.spec>();
+ }
+ }
+ }
+ }
+
+ template<str fmt>
+ consteval auto operator ""_format_to() noexcept
+ {
+ return parseFormat<fmt, 0, 0, 0>();
+ }
+ }
+ }
+
+ using compile_time::operator ""_format_to;
+}
diff --git a/cxxformat/cxxformat.hpp b/cxxformat/cxxformat.hpp
new file mode 100644
index 0000000..3e6fe21
--- /dev/null
+++ b/cxxformat/cxxformat.hpp
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "core.hpp"
+#include "formatters.hpp"
+#include "file_ptr.hpp"
+#include "ostream.hpp"
+#include "string.hpp"
diff --git a/cxxformat/file_ptr.hpp b/cxxformat/file_ptr.hpp
new file mode 100644
index 0000000..53c8a9a
--- /dev/null
+++ b/cxxformat/file_ptr.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "core.hpp"
+
+#include <cstdio>
+
+namespace format {
+ namespace {
+ class FilePtrOutput : public FormatOutputBase<FilePtrOutput> {
+ FILE* to;
+
+ public:
+ FilePtrOutput(FILE* to) noexcept : to{to} {}
+
+ void output(std::string_view what) const
+ {
+ fwrite(what.data(), what.size(), 1, to);
+ }
+ };
+ }
+
+}
+[[maybe_unused]] format::FilePtrOutput make_output(FILE* to) noexcept { return {to}; }
diff --git a/cxxformat/formatters.hpp b/cxxformat/formatters.hpp
new file mode 100644
index 0000000..ad253f6
--- /dev/null
+++ b/cxxformat/formatters.hpp
@@ -0,0 +1,480 @@
+#pragma once
+
+#include "core.hpp"
+
+#include <concepts>
+
+namespace format {
+ namespace {
+ constexpr void formatPadded(const format_output auto& out, std::string_view subject, char padding, bool leftJustified, optional_int<std::size_t> minWidth)
+ {
+ if(subject.length() < minWidth.value_or(0) && !leftJustified)
+ {
+ out(padding, *minWidth - subject.length());
+ }
+
+ out(subject);
+
+ if(subject.length() < minWidth.value_or(0) && leftJustified)
+ {
+ out(padding, *minWidth - subject.length());
+ }
+ }
+ }
+
+ using invalid_argument = std::invalid_argument;
+
+ template<std::integral T>
+ struct formatter<T> {
+ static constexpr void format(const format_output auto& out, T t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ int base = 10;
+ switch(spec.conversion)
+ {
+ case 'v':
+ break;
+ case 'c':
+ {
+ // TODO: see static_assert version
+ char converted = static_cast<char>(static_cast<unsigned char>(t));
+ formatPadded(out, {&converted, 1}, ' ', spec.leftJustified, minWidth);
+ return;
+ }
+ case 'i': case 'd':
+ if constexpr (std::unsigned_integral<T>)
+ {
+ throw invalid_argument{"%i and %d are not supported for unsigned integrals"};
+ }
+ break;
+ case 'o':
+ base = 8;
+ if constexpr (std::signed_integral<T>)
+ {
+ throw invalid_argument{"%o is not supported for signed integrals"};
+ }
+ break;
+ case 'x': case 'X':
+ base = 16;
+ if constexpr (std::signed_integral<T>)
+ {
+ throw invalid_argument{"%x and %X are not supported for signed integrals"};
+ }
+ break;
+ case 'u':
+ if constexpr (std::signed_integral<T>)
+ {
+ throw invalid_argument{"%u is not supported for signed integrals"};
+ }
+ break;
+ default:
+ throw invalid_argument{std::string{"The given integral type does not support conversion specifier ‘"} + spec.conversion + std::string{"’"}};
+ }
+
+ std::string_view converted;
+ std::array<char, (std::numeric_limits<T>::digits + 2) / 3> result;
+ if (precision != 0 || t != 0)
+ {
+ const auto [end, ec] = std::to_chars(result.data(), result.data() + result.size(), t, base);
+ assert(ec == std::errc{});
+ converted = {result.data(), end};
+ }
+ const bool hasMinusSign = !std::unsigned_integral<T> && converted.starts_with('-');
+ if (hasMinusSign)
+ {
+ converted.remove_prefix(1);
+ }
+
+ if (spec.conversion == 'X')
+ {
+ std::transform(result.begin(), result.end(), result.data(), [](char c)
+ {
+ return (c >= 'a' && c <= 'z') ? c + ('A' - 'a') : c;
+ });
+ }
+
+ char signPrefix{'\0'};
+ std::string_view prefix;
+ if (hasMinusSign)
+ {
+ prefix = "-";
+ }
+ else if (base != 10 && spec.alternative)
+ {
+ if (spec.conversion == 'o')
+ {
+ prefix = "0";
+ }
+ else if (precision != 0 || t != 0)
+ {
+ if (spec.conversion == 'x')
+ {
+ prefix = "0x";
+ }
+ else if (spec.conversion == 'X')
+ {
+ prefix = "0X";
+ }
+ }
+ }
+ else if (spec.addSign != '\0')
+ {
+ signPrefix = spec.addSign;
+ prefix = {&signPrefix, 1};
+ }
+
+ std::size_t precisionPadding{0};
+ std::size_t normalPadding{0};
+ if (precision)
+ {
+ if (*precision > converted.size())
+ {
+ precisionPadding = *precision - converted.size();
+ }
+ }
+ else if (minWidth && (spec.alternative && base != 10 && spec.padding == '0'))
+ {
+ const auto totalLength = converted.size() + prefix.size();
+ if (*minWidth > totalLength)
+ {
+ precisionPadding = *minWidth - totalLength;
+ minWidth.clear();
+ }
+ }
+
+ if (minWidth)
+ {
+ const auto totalLength = converted.size() + precisionPadding + prefix.size();
+ if (*minWidth > totalLength)
+ {
+ normalPadding = *minWidth - totalLength;
+ }
+ }
+
+ const auto padding = precision ? ' ' : spec.padding;
+ if(!spec.leftJustified)
+ {
+ out(padding, normalPadding);
+ }
+
+ out(prefix);
+ out('0', precisionPadding);
+
+ out(converted);
+
+ if(spec.leftJustified)
+ {
+ out(padding, normalPadding);
+ }
+ return;
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, T t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ constexpr auto conv = spec.conversion;
+ if constexpr (conv == 'c')
+ {
+ // TODO: what is the right thing to do here?
+ // static_assert(std::same_as<T, int>, "%c only accepts int as argument");
+ }
+ else if constexpr (conv == 'i' || conv == 'd')
+ {
+ static_assert(std::signed_integral<T>, "%i and %d only accept signed integral types");
+ }
+ else if constexpr (conv == 'o' || conv == 'x' || conv == 'X' || conv == 'u')
+ {
+ static_assert(std::unsigned_integral<T>, "%o, %x, %X and %u only accept unsigned integral types");
+ }
+ else if constexpr (conv != 'v')
+ {
+ static_assert(sizeof(T) == 0, "Unsupported conversion specified");
+ }
+
+ static_assert(spec.addSign == '\0' || conv == 'i' || conv == 'd', "‘+’ and ‘ ’ (sign related) flags are only allowed for %i and %d");
+ static_assert(!spec.alternative || conv == 'o' || conv == 'x' || conv == 'X', "‘#’ (alternative) flag is only allowed for %x, %X and %o");
+
+ return format(out, t, spec, minWidth, precision);
+ }
+ };
+
+ // NOTE: Difference to printf: no precision uses std::to_chars’ minimal width for exact representation instead of default precision of 6
+ template<std::floating_point T>
+ struct formatter<T> {
+ static constexpr void format(const format_output auto& out, T t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ std::chars_format format;
+ bool uppercase = false;
+ switch(spec.conversion)
+ {
+ case 'A':
+ uppercase = true;
+ [[fallthrough]];
+ case 'a':
+ format = std::chars_format::hex;
+ break;
+ case 'E':
+ uppercase = true;
+ [[fallthrough]];
+ case 'e':
+ format = std::chars_format::scientific;
+ break;
+ case 'F':
+ uppercase = true;
+ [[fallthrough]];
+ case 'f':
+ format = std::chars_format::fixed;
+ break;
+ case 'G':
+ uppercase = true;
+ [[fallthrough]];
+ case 'g':
+ case 'v':
+ format = std::chars_format::general;
+ break;
+ default:
+ throw invalid_argument{std::string{"The given floating point type does not support conversion specifier ‘"} + spec.conversion + std::string{"’"}};
+ }
+
+ std::array<char, (std::numeric_limits<T>::digits10 * 2 + 6)> result;
+ const auto [end, ec] = precision ? std::to_chars(result.data(), result.data() + result.size(), t, format ,*precision) : std::to_chars(result.data(), result.data() + result.size(), t, format);
+ assert(ec == std::errc{});
+ std::string_view converted{result.data(), end};
+
+ const bool hasMinusSign = converted.starts_with('-');
+ if (hasMinusSign)
+ {
+ converted.remove_prefix(1);
+ }
+
+ std::string_view mantissa = converted;
+ std::string_view exponent;
+ if (const auto exponentPos = converted.find(format == std::chars_format::hex ? 'p' : 'e'); exponentPos != std::string_view::npos)
+ {
+ mantissa = converted.substr(0, exponentPos);
+ exponent = converted.substr(exponentPos);
+ }
+
+ const bool hasDot = mantissa.find('.') != std::string_view::npos;
+ const bool addDot = spec.alternative && !hasDot;
+
+ std::size_t extraZeros{0};
+ if (spec.alternative && format == std::chars_format::general && precision)
+ {
+ const auto currentMantissaPrecision = mantissa.size() - (hasDot ? 1 : 0);
+ if (*precision > currentMantissaPrecision)
+ {
+ extraZeros = *precision - currentMantissaPrecision;
+ }
+ }
+
+ if (uppercase)
+ {
+ std::transform(result.data(), end, result.data(), [](char c)
+ {
+ return (c >= 'a' && c <= 'z') ? c + ('A' - 'a') : c;
+ });
+ }
+
+ const char signPrefix = hasMinusSign ? '-' : spec.addSign;
+ std::string_view hexPrefix;
+ if (format == std::chars_format::hex && std::isfinite(t))
+ {
+ if (spec.conversion == 'A')
+ {
+ hexPrefix = "0X";
+ }
+ else
+ {
+ hexPrefix = "0x";
+ }
+ }
+
+ std::size_t paddingLength{0};
+ if (minWidth)
+ {
+ const auto totalLength = (signPrefix != '\0' ? 1 : 0) + hexPrefix.size() + converted.size() + (addDot ? 1 : 0) + extraZeros;
+ if (*minWidth > totalLength)
+ {
+ paddingLength = *minWidth - totalLength;
+ }
+ }
+
+ if(!spec.leftJustified && spec.padding != '0')
+ {
+ out(spec.padding, paddingLength);
+ }
+
+ if (signPrefix != '\0')
+ {
+ out(signPrefix);
+ }
+ out(hexPrefix);
+
+ if(!spec.leftJustified && spec.padding == '0')
+ {
+ out(spec.padding, paddingLength);
+ }
+
+ out(mantissa);
+ if (addDot)
+ {
+ out('.');
+ }
+ out('0', extraZeros);
+ out(exponent);
+
+ if(spec.leftJustified)
+ {
+ out(spec.padding, paddingLength);
+ }
+ return;
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, T t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(std::string_view{"fFeEaAgGv"}.find(spec.conversion) != std::string_view::npos, "Unsupported conversion specified for floating point type");
+ return format(out, t, spec, minWidth, precision);
+ }
+ };
+
+ template<typename T>
+ concept stringy = requires(T t) { std::string_view{t}; };
+
+ template<stringy S>
+ struct formatter<S> {
+ static constexpr void format(const format_output auto& out, const S& s, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ if (spec.conversion != 's' && spec.conversion != 'v')
+ {
+ throw invalid_argument{"The given stringy type only supports the ‘s’ and ‘v’ conversions"};
+ }
+ std::string_view val(s);
+ if (precision)
+ {
+ val = val.substr(0, *precision);
+ }
+
+ formatPadded(out, val, ' ', spec.leftJustified, minWidth);
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, const S& s, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(spec.conversion == 's' || spec.conversion == 'v', "The given stringy type only supports the ‘s’ and ‘v’ conversions");
+ return format(out, s, spec, minWidth, precision);
+ }
+ };
+
+ template<typename T>
+ concept pointer = std::is_pointer_v<T>;
+
+ template<pointer T>
+ struct formatter<T> {
+ static constexpr void format(const format_output auto& out, const std::remove_pointer_t<T>* t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ if (t == nullptr)
+ {
+ formatPadded(out, "(nil)", ' ', spec.leftJustified, minWidth);
+ }
+ else
+ {
+ constexpr format_specifier spec{.conversion = 'x', .alternative = true};
+ formatter<std::uintptr_t>::template format<spec>(out, std::bit_cast<std::uintptr_t>(t), minWidth, std::nullopt);
+ }
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, const std::remove_pointer_t<T>* t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(spec.conversion == 'p' || spec.conversion == 'v', "The given pointer type only supports the ‘p’ and ‘v’ conversions");
+ return format(out, t, spec, minWidth, precision);
+ }
+ };
+
+ template<>
+ struct formatter<std::nullptr_t> {
+ static constexpr void format(const format_output auto& out, std::nullptr_t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ formatPadded(out, "(nil)", ' ', spec.leftJustified, minWidth);
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, std::nullptr_t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(spec.conversion == 'p' || spec.conversion == 'v', "The given pointer type only supports the ‘p’ and ‘v’ conversions");
+ return format(out, nullptr, spec, minWidth, precision);
+ }
+ };
+
+ template<>
+ struct formatter<const char*> {
+ static constexpr void format(const format_output auto& out, const char* s, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ if (spec.conversion == 'p')
+ {
+ formatter<void*>::format(out, static_cast<const void*>(s), spec, minWidth, precision);
+ }
+ else
+ {
+ assert(spec.conversion == 's' || spec.alternative == 'v');
+
+ if (s == nullptr)
+ {
+ formatPadded(out, "(nil)", ' ', spec.leftJustified, minWidth);
+ }
+ else
+ {
+ formatter<std::string_view>::format(out, s, spec, minWidth, precision);
+ }
+ }
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, const char* s, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(spec.conversion == 'p' || spec.conversion == 's' || spec.conversion == 'v', "const char* only supports the ‘p’, ‘s’ and ‘v’ conversions");
+ return format(out, s, spec, minWidth, precision);
+ }
+ };
+
+ template<>
+ struct formatter<char*> : formatter<const char*> {};
+
+ template<typename T>
+ concept ostreamable = requires(T t, std::ostream& out) { {out << t} -> std::same_as<std::ostream&>; };
+
+ template<typename T>
+ concept has_ostream = requires(T t) { {t.stream()} -> std::same_as<std::ostream&>; };
+
+ template<ostreamable T>
+ struct formatter<T, true> {
+ static void format(const format_output auto& out, const T& t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t>)
+ {
+ if constexpr (has_ostream<decltype(out)>)
+ {
+ if (!minWidth)
+ {
+ out.stream() << t;
+ return;
+ }
+ }
+
+ std::ostringstream stream;
+ stream << t;
+ formatPadded(out, stream.view(), ' ', spec.leftJustified, minWidth);
+ }
+
+ template<format_specifier spec>
+ static constexpr void format(const format_output auto& out, const T& t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision)
+ {
+ static_assert(spec.conversion == 'v' || spec.conversion == 's', "The operator<<(std::ostream&) fallback only supports conversions ‘s’ and ‘v’");
+ static_assert(!spec.precision, "The operator<<(std::ostream&) fallback does not support specifying an precision");
+ static_assert(spec.addSign == '\0', "The operator<<(std::ostream&) fallback does not support the add sign (‘+‘) flag");
+ static_assert(!spec.alternative, "The operator<<(std::ostream&) fallback does not support the alternative (‘#‘) flag");
+ static_assert(spec.padding == ' ', "The operator<<(std::ostream&) fallback does not support padding with 0");
+ static_assert(spec.length == format_specifier::Length::None, "The operator<<(std::ostream&) fallback does not support specifying any length");
+ return format(out, t, spec, minWidth, precision);
+ }
+ };
+}
diff --git a/cxxformat/helpers.hpp b/cxxformat/helpers.hpp
new file mode 100644
index 0000000..c4bad42
--- /dev/null
+++ b/cxxformat/helpers.hpp
@@ -0,0 +1,259 @@
+#pragma once
+
+#include <cassert>
+#include <concepts>
+#include <memory>
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+namespace detail {
+ template<typename T>
+ class FunctorHelper : T {
+ using T::operator();
+ };
+}
+
+template<typename T>
+concept functor = requires {
+ std::declval<detail::FunctorHelper<T>>();
+};
+
+template<functor... T>
+class overloaded_callable : public T...
+{
+public:
+ overloaded_callable(T&&... bases) : T{std::forward<T>(bases)}... {}
+ using T::operator()...;
+};
+
+template<std::size_t i>
+struct nth_argument_s
+{
+ template<typename Arg, typename... Args>
+ constexpr decltype(auto) operator()(Arg&& arg, Args&&... args) const noexcept
+ {
+ static_assert(i < sizeof...(Args) + 1, "index must be smaller than the number of arguments");
+ if constexpr (i == 0)
+ {
+ return std::forward<Arg>(arg);
+ }
+ else
+ {
+ return nth_argument_s<i - 1>{}(std::forward<Args>(args)...);
+ }
+ }
+};
+
+template<std::size_t i>
+constexpr inline auto nth_argument = nth_argument_s<i>{};
+
+template<std::size_t i, typename Arg, typename... Args>
+struct nth_type_s
+{
+ static_assert(i < sizeof...(Args) + 1, "index must be smaller than the number of arguments");
+ using type = typename nth_type_s<i - 1, Args...>::type;
+};
+
+template<typename Arg, typename... Args>
+struct nth_type_s<0, Arg, Args...>
+{
+ using type = Arg;
+};
+
+template<std::size_t i, typename... Args>
+using nth_type = typename nth_type_s<i, Args...>::type;
+
+template<auto... x>
+struct holder {};
+
+namespace helper_concepts {
+template<typename T, typename U, template <typename> class optional>
+concept optional_ctor_ok = requires {
+ !std::is_constructible_v<T, optional<U>&>;
+ !std::is_constructible_v<T, const optional<U>&>;
+ !std::is_constructible_v<T, optional<U>&&>;
+ !std::is_constructible_v<T, const optional<U>&&>;
+ !std::is_convertible_v<optional<U>&, T>;
+ !std::is_convertible_v<const optional<U>&, T>;
+ !std::is_convertible_v<optional<U>&&, T>;
+ !std::is_convertible_v<const optional<U>&&, T>;
+};
+}
+
+template<typename T>
+struct structural_optional {
+
+ bool hasValue;
+ union {
+ char empty; // structural types can not have uninitialized memory
+ T value;
+ };
+
+ constexpr structural_optional() noexcept : hasValue{false}, empty{} {}
+ constexpr structural_optional(std::nullopt_t) noexcept : hasValue{false}, empty{} {}
+ constexpr structural_optional(const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::copy_constructible<T>) : hasValue{true}, value{value} {}
+ constexpr structural_optional(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::move_constructible<T>) : hasValue{true}, value{std::move(value)} {}
+ template<typename... Args>
+ constexpr structural_optional(std::in_place_t, Args&&... value) noexcept(std::is_nothrow_constructible_v<T, Args...>) requires(std::is_constructible_v<T, Args...>) : hasValue{true}, value{std::forward<Args>(value)...} {}
+ template<typename U>
+ constexpr structural_optional(U&& value) noexcept(std::is_nothrow_convertible_v<U&&, T>) requires(std::is_convertible_v<U&&, T>) : hasValue{true}, value{std::forward<U>(value)} {}
+ template<typename U>
+ constexpr structural_optional(const structural_optional<U>& other) noexcept(std::is_nothrow_constructible_v<T, const U&>) requires(std::is_constructible_v<T, const U&> && helper_concepts::optional_ctor_ok<T, U, structural_optional>) : hasValue{other}, empty{}
+ {
+ if (hasValue)
+ {
+ std::construct_at(&value, *other);
+ }
+ }
+ template<typename U>
+ constexpr structural_optional(structural_optional<U>&& other) noexcept(std::is_nothrow_constructible_v<T, const U&>) requires(std::is_constructible_v<T, U&&> && helper_concepts::optional_ctor_ok<T, U, structural_optional>) : hasValue{other}, empty{}
+ {
+ if (hasValue)
+ {
+ std::construct_at(&value, std::move(*other));
+ }
+ }
+
+ constexpr structural_optional(const structural_optional& other) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::copy_constructible<T>) : hasValue{other.hasValue}, empty{}
+ {
+ if (hasValue)
+ {
+ std::construct_at(&value, other.value);
+ }
+ }
+ constexpr structural_optional(structural_optional&& other) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::move_constructible<T>) : hasValue{other.hasValue}, empty{}
+ {
+ if (hasValue)
+ {
+ std::construct_at(&value, std::move(other.value));
+ }
+ }
+
+ constexpr void clear()
+ {
+ if (hasValue)
+ {
+ value.~T();
+ hasValue = false;
+ empty = {};
+ }
+ }
+
+ explicit constexpr operator bool() const noexcept { return hasValue; }
+ constexpr T& operator*() noexcept
+ {
+ assert(hasValue);
+ return value;
+ }
+
+ constexpr const T& operator*() const noexcept
+ {
+ assert(hasValue);
+ return value;
+ }
+
+ constexpr structural_optional& operator=(const T& val) noexcept(std::is_nothrow_copy_assignable_v<T>) requires(std::assignable_from<T&, const T&>)
+ {
+ emplace(val);
+ return *this;
+ }
+
+ constexpr structural_optional& operator=(T&& val) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::assignable_from<T, T&&>)
+ {
+ emplace(std::move(val));
+ return *this;
+ }
+
+ constexpr structural_optional& operator=(const structural_optional& other) noexcept(std::is_nothrow_copy_assignable_v<T>) requires(std::assignable_from<T&, const T&>)
+ {
+ if (other.hasValue)
+ {
+ emplace(other.value);
+ }
+ else
+ {
+ clear();
+ }
+ return *this;
+ }
+
+ constexpr structural_optional& operator=(structural_optional&& other) noexcept(std::is_nothrow_move_assignable_v<T>) requires(std::assignable_from<T, T&&>)
+ {
+ if (other.hasValue)
+ {
+ emplace(std::move(other.value));
+ }
+ else
+ {
+ clear();
+ }
+ return *this;
+ }
+
+
+ constexpr const T& value_or(const T& fallback) const noexcept { return hasValue ? value : fallback; }
+
+ constexpr bool operator==(const structural_optional& other) const
+ {
+ return hasValue == other.hasValue && (!hasValue || value == other.value);
+ }
+
+ constexpr bool operator==(const T& other) const
+ {
+ return hasValue && value == other;
+ }
+
+ template<typename... Args>
+ constexpr T& emplace(Args&&... args) noexcept
+ {
+ clear();
+ hasValue = true;
+ std::construct_at(&value, std::forward<Args>(args)...);
+ return value;
+ }
+
+ constexpr ~structural_optional()
+ {
+ clear();
+ }
+};
+
+template<std::integral T>
+struct optional_int {
+ T val;
+
+ constexpr optional_int() noexcept : val{emptyVal} {}
+ constexpr optional_int(std::nullopt_t) noexcept : val{emptyVal} {}
+ constexpr optional_int(T val) noexcept : val{val} {}
+ constexpr optional_int(const optional_int&) noexcept = default;
+ template<std::convertible_to<T> U>
+ constexpr optional_int(const optional_int<U>& other) noexcept : val{other ? other.val : emptyVal} {}
+ constexpr optional_int& operator=(const optional_int&) noexcept = default;
+ template<std::convertible_to<T> U>
+ constexpr optional_int& operator=(optional_int<U> other) noexcept
+ {
+ val = other ? other.val : emptyVal;
+ return *this;
+ }
+
+ static constexpr inline T emptyVal{std::signed_integral<T> ? std::numeric_limits<T>::min() : std::numeric_limits<T>::max()};
+
+ constexpr void clear() noexcept
+ {
+ val = emptyVal;
+ }
+
+ constexpr explicit operator bool() const noexcept { return val != emptyVal; }
+ constexpr bool empty() const noexcept { return !*this; }
+
+ constexpr bool operator==(const optional_int& other) const noexcept = default;
+ constexpr bool operator==(T other) const noexcept
+ {
+ return !empty() && val == other;
+ }
+ constexpr T& emplace() noexcept { return val = 0; }
+ constexpr T& operator*() noexcept { return val; }
+ constexpr T operator*() const noexcept { return val; }
+ constexpr T value_or(T fallback) const noexcept { return empty() ? fallback : val; }
+};
diff --git a/cxxformat/ostream.hpp b/cxxformat/ostream.hpp
new file mode 100644
index 0000000..830da52
--- /dev/null
+++ b/cxxformat/ostream.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "core.hpp"
+
+#include <iostream>
+
+namespace format {
+ namespace {
+ class OstreamOutput : public FormatOutputBase<OstreamOutput> {
+ std::ostream& to;
+
+ public:
+ OstreamOutput(std::ostream& to) noexcept : to{to} {}
+
+ void output(std::string_view what) const
+ {
+ to << what;
+ }
+
+ std::ostream& stream() const noexcept { return to; }
+ };
+ }
+
+}
+namespace std {
+ [[maybe_unused]] format::OstreamOutput make_output(std::ostream& to) noexcept { return {to}; }
+}
diff --git a/cxxformat/string.hpp b/cxxformat/string.hpp
new file mode 100644
index 0000000..c0fa565
--- /dev/null
+++ b/cxxformat/string.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "core.hpp"
+#include "ostream.hpp"
+
+#include <sstream>
+
+namespace format {
+ namespace {
+ namespace compile_time {
+ template<str fmt>
+ consteval auto operator ""_format() noexcept
+ {
+ return [formatter = parseFormat<fmt, 0, 0, 0>()]<typename... Args>(Args&&... args) -> std::string
+ {
+ std::ostringstream out;
+ formatter(out, std::forward<Args>(args)...);
+ return out.str();
+ };
+ }
+ }
+ }
+
+ using compile_time::operator ""_format;
+}
diff --git a/main.cpp b/main.cpp
index 2431b73..b858b85 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,54 +1,106 @@
-#include "cxxformat.hpp"
-#include <array>
-#include <utility>
#include <iostream>
+#include <sstream>
-template<size_t N, typename T>
-struct format::FormatConvert<std::string, T[N], std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, char>>>
+#include "cxxformat/cxxformat.hpp"
+
+void numberTests()
{
- template<size_t _N>
- static std::string accessConvert(const T *from)
- {
- if constexpr (_N == N)
- {
- return "}";
- }
- else
- {
- return format::format("%v", from[_N]) + (_N + 1 < N ? ", " : "") + accessConvert<_N + 1>(from);
- }
- }
-
- static std::string convert(const T *from)
- {
- return "array[" + std::to_string(N) + "]{" + accessConvert<0>(from);
- }
-};
-
-template<size_t N, typename T>
-struct format::FormatConvert<std::string, std::array<T, N>>
+ using format::parseNumber;
+ static_assert(!parseNumber("", 0).first);
+ static_assert(!parseNumber("-", 0).first);
+ static_assert(parseNumber("1", 0).first == 1);
+ static_assert(parseNumber("10", 0).first == 10);
+ static_assert(parseNumber("10907", 0).first == 10907);
+ static_assert(parseNumber("-1", 0).first == -1);
+ static_assert(parseNumber("-11097", 0).first == -11097);
+}
+
+int main(int argc, char* argv[])
{
- static std::string convert(const std::array<T, N> &from)
- {
- return format::FormatConvert<std::string, T[N]>::convert(from.data());
- }
-};
+ using namespace format;
+ "-%20g-\n"_format_to(stdout, 0x1.8p0);
+ "-%20.0a-\n"_format_to(stdout, 0.0);
+ "-%#+20.0a-\n"_format_to(stdout, 1.0);
+ "-%#+20.0e-\n"_format_to(stdout, 1.0);
+ "-%#+20.0f-\n"_format_to(stdout, 1.0);
+ "-%#+20.0g-\n"_format_to(stdout, 1.0);
+ "-%#+20.3g-\n"_format_to(stdout, 1.0);
+ "-%+20.0a-\n"_format_to(stdout, 1.0);
+ "-%+20.0e-\n"_format_to(stdout, 1.0);
+ "-%+20.0f-\n"_format_to(stdout, 1.0);
+ "-%+20.3g-\n"_format_to(stdout, 1.0);
+ "-%020.3e-\n"_format_to(stdout, 1.2);
+ "-%020.3f-\n"_format_to(stdout, 1.2);
+ "-%020.3g-\n"_format_to(stdout, 1.2);
+ "-%020.3a-\n"_format_to(stdout, 1.2);
+ "-%020.3a-\n"_format_to(stdout, -1.2);
+ "-%20.3a-\n"_format_to(stdout, -1.2);
+ "-%-20.3a-\n"_format_to(stdout, -1.2);
+ "-%+020.3a-\n"_format_to(stdout, -1.2);
+ "-%+020.3a-\n"_format_to(stdout, 1.2);
+ "-% 020.3a-\n"_format_to(stdout, 1.2);
+ "-%20.3e-%20.3e-\n"_format_to(stdout, 1.234567890123456789, 1.2);
+ "-%p-\n"_format_to(stdout, nullptr);
+ "-%p-\n"_format_to(stdout, argv);
+ "-%p-\n"_format_to(stdout, argv[0]);
+ "-%s-\n"_format_to(stdout, argv[0]);
+ "-%#05x-\n"_format_to(stdout, 10u);
+ "-%1$5.3s--%1$-5.3s-\n"_format_to(stdout, "Hallo");
+ "-%1$5.10s--%1$-5.10s-\n"_format_to(stdout, "Hallo");
+ "%1$d %1$-*3$d %1$d %2$d\n"_format_to(stdout, 1, 4, 10u);
+ "%1$u %*d %1$u %d\n"_format_to(stdout, 5u, 2, 3);
+ "%1$u %-*d %1$u %d\n"_format_to(stdout, 5u, 2, 3);
+ "%1$u %0*d %1$u %d\n"_format_to(stdout, 5u, 2, 3);
+ "%1$u %0-*d %1$u %d\n"_format_to(stdout, 5u, 2, 3);
+ "%1$u %-0*d %1$u %d\n"_format_to(stdout, 5u, 2, 3);
+ "%u % d % d %d\n"_format_to(stdout, 5u, 2, -2, 4);
+ "%u %+d %+d %d\n"_format_to(stdout, 5u, 2, -2, 4);
+ "%d %.0d %.d %0d\n"_format_to(stdout, 0, 0, 0, 1);
+ std::cout << "Hallo %%du %1$s %s%c %s %u = %4$#x = %4$#X = %4$#o %f!\n"_format("Knirp", 's', argv[0], 42u, 1.337) << std::flush;
+ "-%#09x-\n"_format_to(std::cout, 10u);
-template<size_t N, typename T>
-struct format::AutoConversion<std::array<T, N>> : format::SimpleAutoConversion<'s', const std::array<T, N> &> {};
+#define TEST(fmt, ...) do{ char* plain; asprintf(&plain, fmt, __VA_ARGS__); assert(std::string_view{plain} == operator""_format<fmt>()(__VA_ARGS__)); free(plain); }while(0)
-template<size_t N, typename T>
-struct format::AutoConversion<T[N], std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, char>>> : format::SimpleAutoConversion<'s', const T(&)[N]> {};
+ TEST("%d", 1);
+ TEST("%1$d %1$-*3$d %1$d %2$d\n", 1, 2, 10u);
+ TEST("%d %.0d %.d %0d\n", 0, 0, 0, 1);
+ TEST("%1$a %1$A\n", 1.5);
+ TEST("%1$a %1$A\n", 0.0/0.0);
+ TEST("-%1$5.3s--%1$-5.3s-\n", "Hallo");
+ TEST("-%1$5.10s--%1$-5.10s-\n", "Hallo");
+ TEST("-%1$5c--%1$-5c-\n", 55);
+ TEST("-%#09.3x-", 10u);
+ TEST("-%#05x-\n", 10u);
+ TEST("-%#05o-\n", 10u);
+ TEST("-%.0o-\n", 0u);
+ TEST("-%.0x-\n", 0u);
+ TEST("-%.0X-\n", 0u);
+ TEST("-%#.0o-\n", 0u);
+ TEST("-%#.0x-\n", 0u);
+ TEST("-%#.0X-\n", 0u);
+ TEST("-%.0u-\n", 0u);
+ TEST("-%.0i-\n", 0);
+ TEST("-%.0d-\n", 0);
-int main(int, char *[])
-{
- constexpr std::array<int, 5> array[] = {std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}};
-
- std::cout << format::format<"Hello %s: %d %03.5f %V">("World", 7, 3.5, array) << std::endl;
- std::cout << format::format<"%v%v%v%v%v">('H', 'e', 'l', 'l', 'o') << std::endl;
- std::cout << format::format("%V", &array) << std::endl;
- std::cout << format::format("%s", std::array{1, 2, 3, 4, 5}) << std::endl;
- std::cout << format::format("Hello %v: %-7v %v", "World", 3.5, 5) << std::endl;
- std::cout << format::format("Hello %s: %d %03.5f", "World", 7, 3.5) << std::endl;
- return 0;
+ TEST("-%020.3e-\n", -1.2);
+ TEST("-%020.3f-\n", -1.2);
+ TEST("-%020.3g-\n", -1.2);
+ TEST("-%+020.3a-\n", 1.2);
+ TEST("-% 020.3a-\n", 1.2);
+ TEST("-% 20.3a-\n", 1.2);
+ TEST("-%+20.3a-\n", 1.2);
+ TEST("-% -20.3a-\n", 1.2);
+
+ TEST("-%0.0a-\n", 0.0);
+ TEST("-%#0.0e-\n", 0.0);
+ TEST("-%0.0f-\n", 0.0);
+ TEST("-%#020.3g-\n", -1.2);
+ TEST("-%+-20.3a-\n", 1.2);
+ TEST("-%#+20.3g-\n", 1.0);
+
+ // these might fail, because %p format is implementation defined, although POSIX seems to define it as %#x
+ TEST("-%p-\n", nullptr);
+ TEST("-%p-\n", argv);
+ TEST("-%p-\n", argv[0]);
+#undef TEST
}